Merge "run-as: reduce the scope of changed egid." into main
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index d71fc6c..15f09b3 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -382,8 +382,10 @@
           return "SEGV_MTEAERR";
         case SEGV_MTESERR:
           return "SEGV_MTESERR";
+        case SEGV_CPERR:
+          return "SEGV_CPERR";
       }
-      static_assert(NSIGSEGV == SEGV_MTESERR, "missing SEGV_* si_code");
+      static_assert(NSIGSEGV == SEGV_CPERR, "missing SEGV_* si_code");
       break;
     case SIGSYS:
       switch (si->si_code) {
diff --git a/fs_mgr/include_fstab b/fs_mgr/include_fstab
deleted file mode 120000
index 728737f..0000000
--- a/fs_mgr/include_fstab
+++ /dev/null
@@ -1 +0,0 @@
-libfstab/include
\ No newline at end of file
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 6fad662..ac58ba0 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -198,6 +198,7 @@
         "libsnapshot_cow/cow_format.cpp",
         "libsnapshot_cow/cow_reader.cpp",
         "libsnapshot_cow/parser_v2.cpp",
+        "libsnapshot_cow/parser_v3.cpp",
         "libsnapshot_cow/snapshot_reader.cpp",
         "libsnapshot_cow/writer_base.cpp",
         "libsnapshot_cow/writer_v2.cpp",
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index c9777a3..91e0425 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -99,8 +99,10 @@
     uint64_t sequence_buffer_offset;
     // Size, in bytes, of the CowResumePoint buffer.
     uint32_t resume_buffer_size;
-    // Size, in bytes, of the CowOperation buffer.
-    uint32_t op_buffer_size;
+    // Number of CowOperationV3 structs in the operation buffer, currently and total
+    // region size.
+    uint32_t op_count;
+    uint32_t op_count_max;
     // Compression Algorithm
     uint32_t compression_algorithm;
 } __attribute__((packed));
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index debaf36..1ab6ada 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -145,6 +145,7 @@
                      size_t ignore_bytes = 0) override;
 
     CowHeader& GetHeader() override { return header_; }
+    const CowHeaderV3& header_v3() const { return header_; }
 
     bool GetRawBytes(const CowOperation* op, void* buffer, size_t len, size_t* read);
     bool GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read);
@@ -182,10 +183,9 @@
     uint64_t num_total_data_ops_{};
     uint64_t num_ordered_ops_to_merge_{};
     bool has_seq_ops_{};
-    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> xor_data_loc_;
     ReaderFlags reader_flag_;
     bool is_merge_{};
-    uint8_t compression_type_ = kCowCompressNone;
 };
 
 // Though this function takes in a CowHeaderV3, the struct could be populated as a v1/v2 CowHeader.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index 3016e93..5b1e56c 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -54,6 +54,9 @@
 
     // Batch write cluster ops
     bool batch_write = false;
+
+    // Size of the cow operation buffer; used in v3 only.
+    uint32_t op_count_max = 0;
 };
 
 // Interface for writing to a snapuserd COW. All operations are ordered; merges
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index 3b84c95..cacd29d 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -28,8 +28,8 @@
 #include <zlib.h>
 
 #include "cow_decompress.h"
-#include "libsnapshot/cow_format.h"
 #include "parser_v2.h"
+#include "parser_v3.h"
 
 namespace android {
 namespace snapshot {
@@ -82,10 +82,9 @@
     cow->merge_op_start_ = merge_op_start_;
     cow->num_total_data_ops_ = num_total_data_ops_;
     cow->num_ordered_ops_to_merge_ = num_ordered_ops_to_merge_;
-    cow->data_loc_ = data_loc_;
+    cow->xor_data_loc_ = xor_data_loc_;
     cow->block_pos_index_ = block_pos_index_;
     cow->is_merge_ = is_merge_;
-    cow->compression_type_ = compression_type_;
     return cow;
 }
 
@@ -104,11 +103,14 @@
         PLOG(ERROR) << "lseek header failed";
         return false;
     }
-    if (!android::base::ReadFully(fd_, &header_, sizeof(header_))) {
+
+    CHECK_GE(header_.prefix.header_size, sizeof(CowHeader));
+    CHECK_LE(header_.prefix.header_size, sizeof(header_));
+
+    if (!android::base::ReadFully(fd_, &header_, header_.prefix.header_size)) {
         PLOG(ERROR) << "read header failed";
         return false;
     }
-
     return true;
 }
 
@@ -124,52 +126,35 @@
         return false;
     }
 
-    CowParserV2 parser;
-    if (!parser.Parse(fd, header_, label)) {
+    std::unique_ptr<CowParserBase> parser;
+    switch (header_.prefix.major_version) {
+        case 1:
+        case 2:
+            parser = std::make_unique<CowParserV2>();
+            break;
+        case 3:
+            parser = std::make_unique<CowParserV3>();
+            break;
+        default:
+            LOG(ERROR) << "Unknown version: " << header_.prefix.major_version;
+            return false;
+    }
+    if (!parser->Parse(fd, header_, label)) {
         return false;
     }
 
-    footer_ = parser.footer();
-    fd_size_ = parser.fd_size();
-    last_label_ = parser.last_label();
-    data_loc_ = parser.data_loc();
-    ops_ = std::make_shared<std::vector<CowOperation>>(parser.ops()->size());
-
-    // Translate the operation buffer from on disk to in memory
-    for (size_t i = 0; i < parser.ops()->size(); i++) {
-        const auto& v2_op = parser.ops()->at(i);
-
-        auto& new_op = ops_->at(i);
-        new_op.type = v2_op.type;
-        new_op.data_length = v2_op.data_length;
-
-        if (v2_op.new_block > std::numeric_limits<uint32_t>::max()) {
-            LOG(ERROR) << "Out-of-range new block in COW op: " << v2_op;
-            return false;
-        }
-        new_op.new_block = v2_op.new_block;
-
-        uint64_t source_info = v2_op.source;
-        if (new_op.type != kCowLabelOp) {
-            source_info &= kCowOpSourceInfoDataMask;
-            if (source_info != v2_op.source) {
-                LOG(ERROR) << "Out-of-range source value in COW op: " << v2_op;
-                return false;
-            }
-        }
-        if (v2_op.compression != kCowCompressNone) {
-            if (compression_type_ == kCowCompressNone) {
-                compression_type_ = v2_op.compression;
-            } else if (compression_type_ != v2_op.compression) {
-                LOG(ERROR) << "COW has mixed compression types which is not supported;"
-                           << " previously saw " << compression_type_ << ", got "
-                           << v2_op.compression << ", op: " << v2_op;
-                return false;
-            }
-        }
-        new_op.source_info = source_info;
+    TranslatedCowOps ops_info;
+    if (!parser->Translate(&ops_info)) {
+        return false;
     }
 
+    header_ = ops_info.header;
+    ops_ = std::move(ops_info.ops);
+    footer_ = parser->footer();
+    fd_size_ = parser->fd_size();
+    last_label_ = parser->last_label();
+    xor_data_loc_ = parser->xor_data_loc();
+
     // If we're resuming a write, we're not ready to merge
     if (label.has_value()) return true;
     return PrepMergeOps();
@@ -615,8 +600,8 @@
 
 bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) {
     // Validate the offset, taking care to acknowledge possible overflow of offset+len.
-    if (offset < header_.prefix.header_size || offset >= fd_size_ - sizeof(CowFooter) ||
-        len >= fd_size_ || offset + len > fd_size_ - sizeof(CowFooter)) {
+    if (offset < header_.prefix.header_size || offset >= fd_size_ || offset + len > fd_size_ ||
+        len >= fd_size_) {
         LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes";
         return false;
     }
@@ -664,7 +649,7 @@
 };
 
 uint8_t CowReader::GetCompressionType() {
-    return compression_type_;
+    return header_.compression_algorithm;
 }
 
 ssize_t CowReader::ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
@@ -696,12 +681,11 @@
 
     uint64_t offset;
     if (op->type == kCowXorOp) {
-        offset = data_loc_->at(op->new_block);
+        offset = xor_data_loc_->at(op->new_block);
     } else {
         offset = GetCowOpSourceInfoData(*op);
     }
-
-    if (!decompressor) {
+    if (!decompressor || op->data_length == header_.block_size) {
         CowDataStream stream(this, offset + ignore_bytes, op->data_length - ignore_bytes);
         return stream.ReadFully(buffer, buffer_size);
     }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
index a291469..993630b 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
@@ -74,15 +74,16 @@
     }
 }
 
-static bool ShowRawOpStreamV2(borrowed_fd fd, const CowHeader& header) {
+static bool ShowRawOpStreamV2(borrowed_fd fd, const CowHeaderV3& header) {
     CowParserV2 parser;
     if (!parser.Parse(fd, header)) {
         LOG(ERROR) << "v2 parser failed";
         return false;
     }
-    for (const auto& op : *parser.ops()) {
+    for (const auto& op : *parser.get_v2ops()) {
         std::cout << op << "\n";
-        if (auto iter = parser.data_loc()->find(op.new_block); iter != parser.data_loc()->end()) {
+        if (auto iter = parser.xor_data_loc()->find(op.new_block);
+            iter != parser.xor_data_loc()->end()) {
             std::cout << "    data loc: " << iter->second << "\n";
         }
     }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_base.h b/fs_mgr/libsnapshot/libsnapshot_cow/parser_base.h
new file mode 100644
index 0000000..837b33e
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_base.h
@@ -0,0 +1,56 @@
+//
+// Copyright (C) 2023 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 <optional>
+#include <unordered_map>
+
+#include <android-base/unique_fd.h>
+#include <libsnapshot/cow_format.h>
+
+namespace android {
+namespace snapshot {
+
+struct TranslatedCowOps {
+    CowHeaderV3 header;
+    std::shared_ptr<std::vector<CowOperationV3>> ops;
+};
+
+class CowParserBase {
+  public:
+    virtual ~CowParserBase() = default;
+
+    virtual bool Parse(android::base::borrowed_fd fd, const CowHeaderV3& header,
+                       std::optional<uint64_t> label = {}) = 0;
+    virtual bool Translate(TranslatedCowOps* out) = 0;
+    virtual std::optional<CowFooter> footer() const { return std::nullopt; }
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> xor_data_loc() {
+        return xor_data_loc_;
+    };
+
+    uint64_t fd_size() const { return fd_size_; }
+    const std::optional<uint64_t>& last_label() const { return last_label_; }
+
+  protected:
+    CowHeaderV3 header_ = {};
+    uint64_t fd_size_;
+    std::optional<uint64_t> last_label_;
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> xor_data_loc_ = {};
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
index 8f20317..08a43a4 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
@@ -18,12 +18,14 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 
+#include <libsnapshot/cow_format.h>
+
 namespace android {
 namespace snapshot {
 
 using android::base::borrowed_fd;
 
-bool CowParserV2::Parse(borrowed_fd fd, const CowHeader& header, std::optional<uint64_t> label) {
+bool CowParserV2::Parse(borrowed_fd fd, const CowHeaderV3& header, std::optional<uint64_t> label) {
     auto pos = lseek(fd.get(), 0, SEEK_END);
     if (pos < 0) {
         PLOG(ERROR) << "lseek end failed";
@@ -47,8 +49,7 @@
         return false;
     }
 
-    if ((header_.prefix.major_version > kCowVersionMajor) ||
-        (header_.prefix.minor_version != kCowVersionMinor)) {
+    if (header_.prefix.major_version > 2 || header_.prefix.minor_version != 0) {
         LOG(ERROR) << "Header version mismatch, "
                    << "major version: " << header_.prefix.major_version
                    << ", expected: " << kCowVersionMajor
@@ -62,7 +63,7 @@
 
 bool CowParserV2::ParseOps(borrowed_fd fd, std::optional<uint64_t> label) {
     uint64_t pos;
-    auto data_loc = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
+    auto xor_data_loc = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
 
     // Skip the scratch space
     if (header_.prefix.major_version >= 2 && (header_.buffer_size > 0)) {
@@ -110,7 +111,7 @@
             auto& current_op = ops_buffer->data()[current_op_num];
             current_op_num++;
             if (current_op.type == kCowXorOp) {
-                data_loc->insert({current_op.new_block, data_pos});
+                xor_data_loc->insert({current_op.new_block, data_pos});
             }
             pos += sizeof(CowOperationV2) + GetNextOpOffset(current_op, header_.cluster_ops);
             data_pos += current_op.data_length + GetNextDataOffset(current_op, header_.cluster_ops);
@@ -190,9 +191,51 @@
         }
     }
 
-    ops_ = ops_buffer;
-    ops_->shrink_to_fit();
-    data_loc_ = data_loc;
+    v2_ops_ = ops_buffer;
+    v2_ops_->shrink_to_fit();
+    xor_data_loc_ = xor_data_loc;
+    return true;
+}
+
+bool CowParserV2::Translate(TranslatedCowOps* out) {
+    out->ops = std::make_shared<std::vector<CowOperationV3>>(v2_ops_->size());
+
+    // Translate the operation buffer from on disk to in memory
+    for (size_t i = 0; i < out->ops->size(); i++) {
+        const auto& v2_op = v2_ops_->at(i);
+
+        auto& new_op = out->ops->at(i);
+        new_op.type = v2_op.type;
+        new_op.data_length = v2_op.data_length;
+
+        if (v2_op.new_block > std::numeric_limits<uint32_t>::max()) {
+            LOG(ERROR) << "Out-of-range new block in COW op: " << v2_op;
+            return false;
+        }
+        new_op.new_block = v2_op.new_block;
+
+        uint64_t source_info = v2_op.source;
+        if (new_op.type != kCowLabelOp) {
+            source_info &= kCowOpSourceInfoDataMask;
+            if (source_info != v2_op.source) {
+                LOG(ERROR) << "Out-of-range source value in COW op: " << v2_op;
+                return false;
+            }
+        }
+        if (v2_op.compression != kCowCompressNone) {
+            if (header_.compression_algorithm == kCowCompressNone) {
+                header_.compression_algorithm = v2_op.compression;
+            } else if (header_.compression_algorithm != v2_op.compression) {
+                LOG(ERROR) << "COW has mixed compression types which is not supported;"
+                           << " previously saw " << header_.compression_algorithm << ", got "
+                           << v2_op.compression << ", op: " << v2_op;
+                return false;
+            }
+        }
+        new_op.source_info = source_info;
+    }
+
+    out->header = header_;
     return true;
 }
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
index f51ff88..f9ee2e5 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
@@ -17,36 +17,29 @@
 
 #include <memory>
 #include <optional>
-#include <unordered_map>
 #include <vector>
 
 #include <android-base/unique_fd.h>
 #include <libsnapshot/cow_format.h>
+#include <libsnapshot_cow/parser_base.h>
 
 namespace android {
 namespace snapshot {
 
-class CowParserV2 {
+class CowParserV2 final : public CowParserBase {
   public:
-    bool Parse(android::base::borrowed_fd fd, const CowHeader& header,
-               std::optional<uint64_t> label = {});
+    bool Parse(android::base::borrowed_fd fd, const CowHeaderV3& header,
+               std::optional<uint64_t> label = {}) override;
+    bool Translate(TranslatedCowOps* out) override;
+    std::optional<CowFooter> footer() const override { return footer_; }
 
     const CowHeader& header() const { return header_; }
-    const std::optional<CowFooter>& footer() const { return footer_; }
-    std::shared_ptr<std::vector<CowOperationV2>> ops() { return ops_; }
-    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc() const { return data_loc_; }
-    uint64_t fd_size() const { return fd_size_; }
-    const std::optional<uint64_t>& last_label() const { return last_label_; }
+    std::shared_ptr<std::vector<CowOperationV2>> get_v2ops() { return v2_ops_; }
 
   private:
     bool ParseOps(android::base::borrowed_fd fd, std::optional<uint64_t> label);
-
-    CowHeader header_ = {};
+    std::shared_ptr<std::vector<CowOperationV2>> v2_ops_;
     std::optional<CowFooter> footer_;
-    std::shared_ptr<std::vector<CowOperationV2>> ops_;
-    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
-    uint64_t fd_size_;
-    std::optional<uint64_t> last_label_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
new file mode 100644
index 0000000..a8a63d8
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
@@ -0,0 +1,104 @@
+// Copyright (C) 2023 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 "parser_v3.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+#include <libsnapshot/cow_format.h>
+
+namespace android {
+namespace snapshot {
+
+using android::base::borrowed_fd;
+
+bool CowParserV3::Parse(borrowed_fd fd, const CowHeaderV3& header, std::optional<uint64_t> label) {
+    auto pos = lseek(fd.get(), 0, SEEK_END);
+    if (pos < 0) {
+        PLOG(ERROR) << "lseek end failed";
+        return false;
+    }
+    fd_size_ = pos;
+    header_ = header;
+
+    if (header_.footer_size != 0) {
+        LOG(ERROR) << "Footer size isn't 0, read " << header_.footer_size;
+        return false;
+    }
+    if (header_.op_size != sizeof(CowOperationV3)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperationV3);
+        return false;
+    }
+    if (header_.cluster_ops != 0) {
+        LOG(ERROR) << "Cluster ops not supported in v3";
+        return false;
+    }
+
+    if (header_.prefix.major_version != 3 || header_.prefix.minor_version != 0) {
+        LOG(ERROR) << "Header version mismatch, "
+                   << "major version: " << header_.prefix.major_version
+                   << ", expected: " << kCowVersionMajor
+                   << ", minor version: " << header_.prefix.minor_version
+                   << ", expected: " << kCowVersionMinor;
+        return false;
+    }
+
+    return ParseOps(fd, label);
+}
+
+off_t CowParserV3::GetDataOffset() const {
+    return sizeof(CowHeaderV3) + header_.buffer_size + header_.op_count_max * sizeof(CowOperation);
+}
+
+bool CowParserV3::ParseOps(borrowed_fd fd, std::optional<uint64_t> label) {
+    ops_ = std::make_shared<std::vector<CowOperationV3>>();
+    ops_->resize(header_.op_count);
+
+    const off_t offset = header_.prefix.header_size + header_.buffer_size;
+    if (!android::base::ReadFullyAtOffset(fd, ops_->data(), ops_->size() * sizeof(CowOperationV3),
+                                          offset)) {
+        PLOG(ERROR) << "read ops failed";
+        return false;
+    }
+
+    // fill out mapping of XOR op data location
+    uint64_t data_pos = GetDataOffset();
+
+    xor_data_loc_ = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
+
+    for (auto op : *ops_) {
+        if (op.type == kCowXorOp) {
+            xor_data_loc_->insert({op.new_block, data_pos});
+        }
+        data_pos += op.data_length;
+    }
+    // :TODO: sequence buffer & resume buffer follow
+    // Once we implement labels, we'll have to discard unused ops and adjust
+    // the header as needed.
+    CHECK(!label);
+
+    ops_->shrink_to_fit();
+
+    return true;
+}
+
+bool CowParserV3::Translate(TranslatedCowOps* out) {
+    out->ops = ops_;
+    out->header = header_;
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.h
new file mode 100644
index 0000000..e2663cc
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2023 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.
+// Copyright (C) 2023 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 <stdint.h>
+
+#include <memory>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <libsnapshot/cow_format.h>
+#include <libsnapshot_cow/parser_base.h>
+
+namespace android {
+namespace snapshot {
+
+class CowParserV3 final : public CowParserBase {
+  public:
+    bool Parse(android::base::borrowed_fd fd, const CowHeaderV3& header,
+               std::optional<uint64_t> label = {}) override;
+    bool Translate(TranslatedCowOps* out) override;
+
+  private:
+    bool ParseOps(android::base::borrowed_fd fd, std::optional<uint64_t> label);
+    off_t GetDataOffset() const;
+    CowHeaderV3 header_ = {};
+    std::shared_ptr<std::vector<CowOperationV3>> ops_;
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
index cc8dd83..d0af0f9 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
@@ -37,7 +37,7 @@
 namespace android {
 namespace snapshot {
 
-class CowOperationV3Test : public ::testing::Test {
+class CowTestV3 : public ::testing::Test {
   protected:
     virtual void SetUp() override {
         cow_ = std::make_unique<TemporaryFile>();
@@ -51,7 +51,12 @@
     std::unique_ptr<TemporaryFile> cow_;
 };
 
-TEST_F(CowOperationV3Test, CowHeaderV2Test) {
+// Helper to check read sizes.
+static inline bool ReadData(CowReader& reader, const CowOperation* op, void* buffer, size_t size) {
+    return reader.ReadData(op, buffer, size) == size;
+}
+
+TEST_F(CowTestV3, CowHeaderV2Test) {
     CowOptions options;
     options.cluster_ops = 5;
     options.num_merge_ops = 1;
@@ -67,11 +72,390 @@
 
     const auto& header = reader.GetHeader();
     ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
-    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
-    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.prefix.major_version, 2);
+    ASSERT_EQ(header.prefix.minor_version, 0);
     ASSERT_EQ(header.block_size, options.block_size);
     ASSERT_EQ(header.cluster_ops, options.cluster_ops);
 }
 
+TEST_F(CowTestV3, Header) {
+    CowOptions options;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.GetHeader();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, 0);
+    ASSERT_EQ(header.block_size, options.block_size);
+    ASSERT_EQ(header.cluster_ops, 0);
+}
+
+TEST_F(CowTestV3, MaxOp) {
+    CowOptions options;
+    options.op_count_max = 20;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    ASSERT_FALSE(writer->AddZeroBlocks(1, 21));
+    ASSERT_FALSE(writer->AddZeroBlocks(1, 1));
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+
+    ASSERT_FALSE(writer->AddRawBlocks(5, data.data(), data.size()));
+
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    ASSERT_EQ(reader.header_v3().op_count, 20);
+}
+
+TEST_F(CowTestV3, ZeroOp) {
+    CowOptions options;
+    options.op_count_max = 20;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    ASSERT_TRUE(writer->AddZeroBlocks(1, 2));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    ASSERT_EQ(reader.header_v3().op_count, 2);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    auto op = iter->Get();
+    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->data_length, 0);
+    ASSERT_EQ(op->new_block, 1);
+    ASSERT_EQ(op->source_info, 0);
+
+    iter->Next();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
+
+    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->data_length, 0);
+    ASSERT_EQ(op->new_block, 2);
+    ASSERT_EQ(op->source_info, 0);
+}
+
+TEST_F(CowTestV3, ReplaceOp) {
+    CowOptions options;
+    options.op_count_max = 20;
+    options.scratch_space = false;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+
+    ASSERT_TRUE(writer->AddRawBlocks(5, data.data(), data.size()));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.block_size, options.block_size);
+    ASSERT_EQ(header.op_count, 1);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    auto op = iter->Get();
+    std::string sink(data.size(), '\0');
+
+    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->data_length, 4096);
+    ASSERT_EQ(op->new_block, 5);
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data);
+}
+
+TEST_F(CowTestV3, ConsecutiveReplaceOp) {
+    CowOptions options;
+    options.op_count_max = 20;
+    options.scratch_space = false;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    std::string data;
+    data.resize(options.block_size * 5);
+    for (int i = 0; i < data.size(); i++) {
+        data[i] = char(rand() % 256);
+    }
+
+    ASSERT_TRUE(writer->AddRawBlocks(5, data.data(), data.size()));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.block_size, options.block_size);
+    ASSERT_EQ(header.op_count, 5);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    size_t i = 0;
+    std::string sink(data.size(), '\0');
+
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type, kCowReplaceOp);
+        ASSERT_EQ(op->data_length, options.block_size);
+        ASSERT_EQ(op->new_block, 5 + i);
+        ASSERT_TRUE(
+                ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size));
+        iter->Next();
+        i++;
+    }
+    ASSERT_EQ(sink, data);
+
+    ASSERT_EQ(i, 5);
+}
+
+TEST_F(CowTestV3, CopyOp) {
+    CowOptions options;
+    options.op_count_max = 100;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+
+    ASSERT_TRUE(writer->AddCopy(10, 1000, 100));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.block_size, options.block_size);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    size_t i = 0;
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type, kCowCopyOp);
+        ASSERT_EQ(op->data_length, 0);
+        ASSERT_EQ(op->new_block, 10 + i);
+        ASSERT_EQ(GetCowOpSourceInfoData(*op), 1000 + i);
+        iter->Next();
+        i += 1;
+    }
+
+    ASSERT_EQ(i, 100);
+}
+
+TEST_F(CowTestV3, XorOp) {
+    CowOptions options;
+    options.op_count_max = 100;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+
+    std::string data = "This is test data-1. Testing xor";
+    data.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer->AddXorBlocks(50, data.data(), data.size(), 24, 10));
+    ASSERT_TRUE(writer->Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.op_count, 1);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
+    std::string sink(data.size(), '\0');
+
+    ASSERT_EQ(op->type, kCowXorOp);
+    ASSERT_EQ(op->data_length, 4096);
+    ASSERT_EQ(op->new_block, 50);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 98314);  // 4096 * 24 + 10
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data);
+}
+
+TEST_F(CowTestV3, ConsecutiveXorOp) {
+    CowOptions options;
+    options.op_count_max = 100;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+
+    std::string data;
+    data.resize(options.block_size * 5);
+    for (int i = 0; i < data.size(); i++) {
+        data[i] = char(rand() % 256);
+    }
+
+    ASSERT_TRUE(writer->AddXorBlocks(50, data.data(), data.size(), 24, 10));
+    ASSERT_TRUE(writer->Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.op_count, 5);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    std::string sink(data.size(), '\0');
+    size_t i = 0;
+
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type, kCowXorOp);
+        ASSERT_EQ(op->data_length, 4096);
+        ASSERT_EQ(op->new_block, 50 + i);
+        ASSERT_EQ(GetCowOpSourceInfoData(*op), 98314 + (i * options.block_size));  // 4096 * 24 + 10
+        ASSERT_TRUE(
+                ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size));
+        iter->Next();
+        i++;
+    }
+    ASSERT_EQ(sink, data);
+
+    ASSERT_EQ(i, 5);
+}
+
+TEST_F(CowTestV3, AllOpsWithCompression) {
+    CowOptions options;
+    options.compression = "gz";
+    options.op_count_max = 100;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+
+    std::string data;
+    data.resize(options.block_size * 5);
+    for (int i = 0; i < data.size(); i++) {
+        data[i] = char(rand() % 4);
+    }
+
+    ASSERT_TRUE(writer->AddZeroBlocks(10, 5));
+    ASSERT_TRUE(writer->AddCopy(15, 3, 5));
+    ASSERT_TRUE(writer->AddRawBlocks(18, data.data(), data.size()));
+    ASSERT_TRUE(writer->AddXorBlocks(50, data.data(), data.size(), 24, 10));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.block_size, options.block_size);
+    ASSERT_EQ(header.buffer_size, BUFFER_REGION_DEFAULT_SIZE);
+    ASSERT_EQ(header.op_count, 20);
+    ASSERT_EQ(header.op_count_max, 100);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    size_t i = 0;
+
+    while (i < 5) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type, kCowZeroOp);
+        ASSERT_EQ(op->new_block, 10 + i);
+        iter->Next();
+        i++;
+    }
+    i = 0;
+    while (i < 5) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type, kCowCopyOp);
+        ASSERT_EQ(op->new_block, 15 + i);
+        ASSERT_EQ(GetCowOpSourceInfoData(*op), 3 + i);
+        iter->Next();
+        i++;
+    }
+    i = 0;
+    std::string sink(data.size(), '\0');
+
+    while (i < 5) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type, kCowReplaceOp);
+        ASSERT_EQ(op->new_block, 18 + i);
+        ASSERT_TRUE(
+                ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size));
+        iter->Next();
+        i++;
+    }
+    ASSERT_EQ(sink, data);
+
+    i = 0;
+    std::fill(sink.begin(), sink.end(), '\0');
+    while (i < 5) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type, kCowXorOp);
+        ASSERT_EQ(op->new_block, 50 + i);
+        ASSERT_EQ(GetCowOpSourceInfoData(*op), 98314 + (i * options.block_size));  // 4096 * 24 + 10
+        ASSERT_TRUE(
+                ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size));
+        iter->Next();
+        i++;
+    }
+    ASSERT_EQ(sink, data);
+}
+
+TEST_F(CowTestV3, GzCompression) {
+    CowOptions options;
+    options.op_count_max = 100;
+    options.compression = "gz";
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+
+    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->Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto header = reader.header_v3();
+    ASSERT_EQ(header.compression_algorithm, kCowCompressGz);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
+
+    std::string sink(data.size(), '\0');
+
+    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->data_length, 56);  // compressed!
+    ASSERT_EQ(op->new_block, 50);
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data);
+
+    iter->Next();
+    ASSERT_TRUE(iter->AtEnd());
+}
 }  // namespace snapshot
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
index 709b248..5274456 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
@@ -62,6 +62,8 @@
     bool InitFd();
     bool ValidateNewBlock(uint64_t new_block);
 
+    bool IsEstimating() const { return is_dev_null_; }
+
     CowOptions options_;
 
     android::base::unique_fd fd_;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
index 83a9b1b..37324c7 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
@@ -273,10 +273,11 @@
     if (!ReadCowHeader(fd_, &header_v3)) {
         return false;
     }
+
     header_ = header_v3;
 
     CowParserV2 parser;
-    if (!parser.Parse(fd_, header_, {label})) {
+    if (!parser.Parse(fd_, header_v3, {label})) {
         return false;
     }
     if (header_.prefix.major_version > 2) {
@@ -292,7 +293,7 @@
     footer_.op.num_ops = 0;
     InitPos();
 
-    for (const auto& op : *parser.ops()) {
+    for (const auto& op : *parser.get_v2ops()) {
         AddOperation(op);
     }
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
index 5ae5f19..86dd9f7 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -78,7 +78,8 @@
     // during COW size estimation
     header_.sequence_buffer_offset = 0;
     header_.resume_buffer_size = 0;
-    header_.op_buffer_size = 0;
+    header_.op_count = 0;
+    header_.op_count_max = 0;
     header_.compression_algorithm = kCowCompressNone;
     return;
 }
@@ -96,6 +97,8 @@
         LOG(ERROR) << "unrecognized compression: " << options_.compression;
         return false;
     }
+    header_.compression_algorithm = *algorithm;
+
     if (parts.size() > 1) {
         if (!android::base::ParseUint(parts[1], &compression_.compression_level)) {
             LOG(ERROR) << "failed to parse compression level invalid type: " << parts[1];
@@ -107,6 +110,7 @@
     }
 
     compression_.algorithm = *algorithm;
+    compressor_ = ICompressor::Create(compression_, header_.block_size);
     return true;
 }
 
@@ -154,42 +158,94 @@
             return false;
         }
     }
+    header_.op_count_max = options_.op_count_max;
 
     if (!Sync()) {
         LOG(ERROR) << "Header sync failed";
         return false;
     }
-
-    next_op_pos_ = 0;
-    next_data_pos_ = 0;
-
+    next_data_pos_ =
+            sizeof(CowHeaderV3) + header_.buffer_size + header_.op_count_max * sizeof(CowOperation);
     return true;
 }
 
 bool CowWriterV3::EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-    if (new_block || old_block || num_blocks) return false;
-    return false;
+    for (size_t i = 0; i < num_blocks; i++) {
+        CowOperationV3 op = {};
+        op.type = kCowCopyOp;
+        op.new_block = new_block + i;
+        op.source_info = old_block + i;
+        if (!WriteOperation(op)) {
+            return false;
+        }
+    }
+
+    return true;
 }
 
 bool CowWriterV3::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-
-    if (new_block_start || data || size) return false;
-    return false;
+    return EmitBlocks(new_block_start, data, size, 0, 0, kCowReplaceOp);
 }
 
 bool CowWriterV3::EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
                                 uint32_t old_block, uint16_t offset) {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-    if (new_block_start || old_block || offset || data || size) return false;
-    return false;
+    return EmitBlocks(new_block_start, data, size, old_block, offset, kCowXorOp);
+}
+
+bool CowWriterV3::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
+                             uint64_t old_block, uint16_t offset, uint8_t type) {
+    const size_t num_blocks = (size / header_.block_size);
+    for (size_t i = 0; i < num_blocks; i++) {
+        const uint8_t* const iter =
+                reinterpret_cast<const uint8_t*>(data) + (header_.block_size * i);
+
+        CowOperation op = {};
+        op.new_block = new_block_start + i;
+
+        op.type = type;
+        if (type == kCowXorOp) {
+            op.source_info = (old_block + i) * header_.block_size + offset;
+        } else {
+            op.source_info = next_data_pos_;
+        }
+        std::basic_string<uint8_t> compressed_data;
+        const void* out_data = iter;
+
+        op.data_length = header_.block_size;
+
+        if (compression_.algorithm) {
+            if (!compressor_) {
+                PLOG(ERROR) << "Compressor not initialized";
+                return false;
+            }
+            compressed_data = compressor_->Compress(out_data, header_.block_size);
+            if (compressed_data.size() < op.data_length) {
+                out_data = compressed_data.data();
+                op.data_length = compressed_data.size();
+            }
+        }
+        if (!WriteOperation(op, out_data, op.data_length)) {
+            PLOG(ERROR) << "AddRawBlocks with compression: write failed. new block: "
+                        << new_block_start << " compression: " << compression_.algorithm;
+            return false;
+        }
+    }
+
+    return true;
 }
 
 bool CowWriterV3::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-    if (new_block_start && num_blocks) return false;
-    return false;
+    for (uint64_t i = 0; i < num_blocks; i++) {
+        CowOperationV3 op;
+        op.type = kCowZeroOp;
+        op.data_length = 0;
+        op.new_block = new_block_start + i;
+        op.source_info = 0;
+        if (!WriteOperation(op)) {
+            return false;
+        }
+    }
+    return true;
 }
 
 bool CowWriterV3::EmitLabel(uint64_t label) {
@@ -204,9 +260,44 @@
     return false;
 }
 
+bool CowWriterV3::WriteOperation(const CowOperationV3& op, const void* data, size_t size) {
+    if (IsEstimating()) {
+        header_.op_count++;
+        header_.op_count_max++;
+        return true;
+    }
+
+    if (header_.op_count + 1 > header_.op_count_max) {
+        LOG(ERROR) << "Maximum number of ops reached: " << header_.op_count_max;
+        return false;
+    }
+
+    const off_t offset = GetOpOffset(header_.op_count);
+    if (!android::base::WriteFullyAtOffset(fd_, &op, sizeof(op), offset)) {
+        PLOG(ERROR) << "write failed for " << op << " at " << offset;
+        return false;
+    }
+    if (data && size > 0) {
+        if (!android::base::WriteFullyAtOffset(fd_, data, size, next_data_pos_)) {
+            PLOG(ERROR) << "write failed for data of size: " << size
+                        << " at offset: " << next_data_pos_;
+            return false;
+        }
+    }
+    header_.op_count++;
+    next_data_pos_ += op.data_length;
+    next_op_pos_ += sizeof(CowOperationV3);
+
+    return true;
+}
+
 bool CowWriterV3::Finalize() {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-    return false;
+    CHECK_GE(header_.prefix.header_size, sizeof(CowHeaderV3));
+    CHECK_LE(header_.prefix.header_size, sizeof(header_));
+    if (!android::base::WriteFullyAtOffset(fd_, &header_, header_.prefix.header_size, 0)) {
+        return false;
+    }
+    return Sync();
 }
 
 uint64_t CowWriterV3::GetCowSize() {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index 2a88a12..2347e91 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -14,7 +14,8 @@
 
 #pragma once
 
-#include <future>
+#include <android-base/logging.h>
+
 #include "writer_base.h"
 
 namespace android {
@@ -42,16 +43,31 @@
     void SetupHeaders();
     bool ParseOptions();
     bool OpenForWrite();
+    bool WriteOperation(const CowOperationV3& op, const void* data = nullptr, size_t size = 0);
+    bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
+                    uint16_t offset, uint8_t type);
+    bool CompressBlocks(size_t num_blocks, const void* data);
+
+    off_t GetOpOffset(uint32_t op_index) const {
+        CHECK_LT(op_index, header_.op_count_max);
+        return header_.prefix.header_size + header_.buffer_size +
+               (op_index * sizeof(CowOperationV3));
+    }
 
   private:
     CowHeaderV3 header_{};
     CowCompression compression_;
     // in the case that we are using one thread for compression, we can store and re-use the same
     // compressor
+    std::unique_ptr<ICompressor> compressor_;
+    std::vector<std::unique_ptr<CompressWorker>> compress_threads_;
 
     uint64_t next_op_pos_ = 0;
     uint64_t next_data_pos_ = 0;
+    std::vector<std::basic_string<uint8_t>> compressed_buf_;
 
+    // in the case that we are using one thread for compression, we can store and re-use the same
+    // compressor
     int num_compress_threads_ = 1;
 };
 
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index eb4beb7..c639e43 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -3513,6 +3513,11 @@
             return Return::Error();
         }
 
+        if (!android::fs_mgr::WaitForFile(cow_path, 6s)) {
+            LOG(ERROR) << "Timed out waiting for device to appear: " << cow_path;
+            return Return::Error();
+        }
+
         if (it->second.using_snapuserd()) {
             unique_fd fd(open(cow_path.c_str(), O_RDWR | O_CLOEXEC));
             if (fd < 0) {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 8208d67..950d771 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -112,7 +112,7 @@
             return false;
         }
 
-        if (!android::base::WriteFully(cow_fd_, &header, sizeof(CowHeader))) {
+        if (!android::base::WriteFully(cow_fd_, &header, header.prefix.header_size)) {
             SNAP_PLOG(ERROR) << "Write to header failed";
             return false;
         }
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index f070776..662185c 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -108,9 +108,9 @@
     static passwd static_passwd = {
         .pw_name = static_name,
         .pw_dir = static_dir,
-        .pw_shell = static_shell,
         .pw_uid = 0,
         .pw_gid = 0,
+        .pw_shell = static_shell,
     };
 
     for (size_t n = 0; n < android_id_count; ++n) {
diff --git a/init/init.cpp b/init/init.cpp
index 40e2169..0439d22 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -108,6 +108,7 @@
 using android::base::StringPrintf;
 using android::base::Timer;
 using android::base::Trim;
+using android::base::unique_fd;
 using android::fs_mgr::AvbHandle;
 using android::snapshot::SnapshotManager;
 
@@ -116,7 +117,8 @@
 
 static int property_triggers_enabled = 0;
 
-static int signal_fd = -1;
+int sigchld_fd = -1;
+static int sigterm_fd = -1;
 static int property_fd = -1;
 
 struct PendingControlMessage {
@@ -713,8 +715,9 @@
     HandlePowerctlMessage("shutdown,container");
 }
 
-static void HandleSignalFd() {
+static void HandleSignalFd(int signal) {
     signalfd_siginfo siginfo;
+    const int signal_fd = signal == SIGCHLD ? sigchld_fd : sigterm_fd;
     ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo)));
     if (bytes_read != sizeof(siginfo)) {
         PLOG(ERROR) << "Failed to read siginfo from signal_fd";
@@ -748,10 +751,28 @@
     }
 }
 
+static Result<int> CreateAndRegisterSignalFd(Epoll* epoll, int signal) {
+    sigset_t mask;
+    sigemptyset(&mask);
+    sigaddset(&mask, signal);
+    unique_fd signal_fd(signalfd(-1, &mask, SFD_CLOEXEC));
+    if (signal_fd == -1) {
+        return ErrnoError() << "failed to create signalfd for signal " << signal;
+    }
+
+    auto result = epoll->RegisterHandler(
+            signal_fd.get(), [signal]() { HandleSignalFd(signal); }, EPOLLIN | EPOLLPRI);
+    if (!result.ok()) {
+        return result.error();
+    }
+
+    return signal_fd.release();
+}
+
 static void InstallSignalFdHandler(Epoll* epoll) {
     // Applying SA_NOCLDSTOP to a defaulted SIGCHLD handler prevents the signalfd from receiving
     // SIGCHLD when a child process stops or continues (b/77867680#comment9).
-    const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP };
+    const struct sigaction act { .sa_flags = SA_NOCLDSTOP, .sa_handler = SIG_DFL };
     sigaction(SIGCHLD, &act, nullptr);
 
     sigset_t mask;
@@ -774,14 +795,19 @@
         LOG(FATAL) << "Failed to register a fork handler: " << strerror(result);
     }
 
-    signal_fd = signalfd(-1, &mask, SFD_CLOEXEC);
-    if (signal_fd == -1) {
-        PLOG(FATAL) << "failed to create signalfd";
+    Result<int> cs_result = CreateAndRegisterSignalFd(epoll, SIGCHLD);
+    if (!cs_result.ok()) {
+        PLOG(FATAL) << cs_result.error();
     }
+    sigchld_fd = cs_result.value();
+    Service::SetSigchldFd(sigchld_fd);
 
-    constexpr int flags = EPOLLIN | EPOLLPRI;
-    if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd, flags); !result.ok()) {
-        LOG(FATAL) << result.error();
+    if (sigismember(&mask, SIGTERM)) {
+        Result<int> cs_result = CreateAndRegisterSignalFd(epoll, SIGTERM);
+        if (!cs_result.ok()) {
+            PLOG(FATAL) << cs_result.error();
+        }
+        sigterm_fd = cs_result.value();
     }
 }
 
diff --git a/init/init.h b/init/init.h
index 9c7e918..b781167 100644
--- a/init/init.h
+++ b/init/init.h
@@ -28,6 +28,8 @@
 namespace android {
 namespace init {
 
+extern int sigchld_fd;
+
 Parser CreateParser(ActionManager& action_manager, ServiceList& service_list);
 Parser CreateApexConfigParser(ActionManager& action_manager, ServiceList& service_list);
 
diff --git a/init/persistent_properties.cpp b/init/persistent_properties.cpp
index e89244f..6f8a4de 100644
--- a/init/persistent_properties.cpp
+++ b/init/persistent_properties.cpp
@@ -46,6 +46,13 @@
 
 constexpr const char kLegacyPersistentPropertyDir[] = "/data/property";
 
+void AddPersistentProperty(const std::string& name, const std::string& value,
+                           PersistentProperties* persistent_properties) {
+    auto persistent_property_record = persistent_properties->add_properties();
+    persistent_property_record->set_name(name);
+    persistent_property_record->set_value(value);
+}
+
 Result<PersistentProperties> LoadLegacyPersistentProperties() {
     std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(kLegacyPersistentPropertyDir), closedir);
     if (!dir) {
@@ -146,13 +153,6 @@
 
 }  // namespace
 
-void AddPersistentProperty(const std::string& name, const std::string& value,
-                           PersistentProperties* persistent_properties) {
-    auto persistent_property_record = persistent_properties->add_properties();
-    persistent_property_record->set_name(name);
-    persistent_property_record->set_value(value);
-}
-
 Result<PersistentProperties> LoadPersistentPropertyFile() {
     auto file_contents = ReadPersistentPropertyFile();
     if (!file_contents.ok()) return file_contents.error();
@@ -266,8 +266,57 @@
         }
     }
 
-    return *persistent_properties;
+    // loop over to find all staged props
+    auto const staged_prefix = std::string_view("next_boot.");
+    auto staged_props = std::unordered_map<std::string, std::string>();
+    for (const auto& property_record : persistent_properties->properties()) {
+        auto const& prop_name = property_record.name();
+        auto const& prop_value = property_record.value();
+        if (StartsWith(prop_name, staged_prefix)) {
+            auto actual_prop_name = prop_name.substr(staged_prefix.size());
+            staged_props[actual_prop_name] = prop_value;
+        }
+    }
+
+    if (staged_props.empty()) {
+        return *persistent_properties;
+    }
+
+    // if has staging, apply staging and perserve the original prop order
+    PersistentProperties updated_persistent_properties;
+    for (const auto& property_record : persistent_properties->properties()) {
+        auto const& prop_name = property_record.name();
+        auto const& prop_value = property_record.value();
+
+        // don't include staged props anymore
+        if (StartsWith(prop_name, staged_prefix)) {
+            continue;
+        }
+
+        auto iter = staged_props.find(prop_name);
+        if (iter != staged_props.end()) {
+            AddPersistentProperty(prop_name, iter->second, &updated_persistent_properties);
+            staged_props.erase(iter);
+        } else {
+            AddPersistentProperty(prop_name, prop_value, &updated_persistent_properties);
+        }
+    }
+
+    // add any additional staged props
+    for (auto const& [prop_name, prop_value] : staged_props) {
+        AddPersistentProperty(prop_name, prop_value, &updated_persistent_properties);
+    }
+
+    // write current updated persist prop file
+    auto result = WritePersistentPropertyFile(updated_persistent_properties);
+    if (!result.ok()) {
+        LOG(ERROR) << "Could not store persistent property: " << result.error();
+    }
+
+    return updated_persistent_properties;
 }
 
+
+
 }  // namespace init
 }  // namespace android
diff --git a/init/persistent_properties.h b/init/persistent_properties.h
index 7e9d438..11083da 100644
--- a/init/persistent_properties.h
+++ b/init/persistent_properties.h
@@ -25,8 +25,6 @@
 namespace android {
 namespace init {
 
-void AddPersistentProperty(const std::string& name, const std::string& value,
-                           PersistentProperties* persistent_properties);
 PersistentProperties LoadPersistentProperties();
 void WritePersistentProperty(const std::string& name, const std::string& value);
 PersistentProperties LoadPersistentPropertiesFromMemory();
diff --git a/init/persistent_properties_test.cpp b/init/persistent_properties_test.cpp
index e5d26db..5763050 100644
--- a/init/persistent_properties_test.cpp
+++ b/init/persistent_properties_test.cpp
@@ -178,5 +178,37 @@
     EXPECT_FALSE(it == read_back_properties.properties().end());
 }
 
+TEST(persistent_properties, StagedPersistProperty) {
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    persistent_property_filename = tf.path;
+
+    std::vector<std::pair<std::string, std::string>> persistent_properties = {
+        {"persist.sys.locale", "en-US"},
+        {"next_boot.persist.test.numbers", "54321"},
+        {"persist.sys.timezone", "America/Los_Angeles"},
+        {"persist.test.numbers", "12345"},
+        {"next_boot.persist.test.extra", "abc"},
+    };
+
+    ASSERT_RESULT_OK(
+            WritePersistentPropertyFile(VectorToPersistentProperties(persistent_properties)));
+
+    std::vector<std::pair<std::string, std::string>> expected_persistent_properties = {
+        {"persist.sys.locale", "en-US"},
+        {"persist.sys.timezone", "America/Los_Angeles"},
+        {"persist.test.numbers", "54321"},
+        {"persist.test.extra", "abc"},
+    };
+
+    // lock down that staged props are applied
+    auto first_read_back_properties = LoadPersistentProperties();
+    CheckPropertiesEqual(expected_persistent_properties, first_read_back_properties);
+
+    // lock down that other props are not overwritten
+    auto second_read_back_properties = LoadPersistentProperties();
+    CheckPropertiesEqual(expected_persistent_properties, second_read_back_properties);
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/init/property_service.cpp b/init/property_service.cpp
index b08a904..bd74358 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -76,6 +76,10 @@
 #include "system/core/init/property_service.pb.h"
 #include "util.h"
 
+static constexpr char APPCOMPAT_OVERRIDE_PROP_FOLDERNAME[] =
+        "/dev/__properties__/appcompat_override";
+static constexpr char APPCOMPAT_OVERRIDE_PROP_TREE_FILE[] =
+        "/dev/__properties__/appcompat_override/property_info";
 using namespace std::literals;
 
 using android::base::ErrnoError;
@@ -1279,11 +1283,17 @@
         return;
     }
 
-    constexpr static const char kPropertyInfosPath[] = "/dev/__properties__/property_info";
-    if (!WriteStringToFile(serialized_contexts, kPropertyInfosPath, 0444, 0, 0, false)) {
+    if (!WriteStringToFile(serialized_contexts, PROP_TREE_FILE, 0444, 0, 0, false)) {
         PLOG(ERROR) << "Unable to write serialized property infos to file";
     }
-    selinux_android_restorecon(kPropertyInfosPath, 0);
+    selinux_android_restorecon(PROP_TREE_FILE, 0);
+
+    mkdir(APPCOMPAT_OVERRIDE_PROP_FOLDERNAME, S_IRWXU | S_IXGRP | S_IXOTH);
+    if (!WriteStringToFile(serialized_contexts, APPCOMPAT_OVERRIDE_PROP_TREE_FILE, 0444, 0, 0,
+                           false)) {
+        PLOG(ERROR) << "Unable to write vendor overrides to file";
+    }
+    selinux_android_restorecon(APPCOMPAT_OVERRIDE_PROP_TREE_FILE, 0);
 }
 
 static void ExportKernelBootProps() {
@@ -1396,33 +1406,12 @@
     switch (init_message.msg_case()) {
         case InitMessage::kLoadPersistentProperties: {
             load_override_properties();
-            // Read persistent properties after all default values have been loaded.
-            // Apply staged and persistent properties
-            bool has_staged_prop = false;
-            auto const staged_prefix = std::string_view("next_boot.");
 
             auto persistent_properties = LoadPersistentProperties();
             for (const auto& property_record : persistent_properties.properties()) {
                 auto const& prop_name = property_record.name();
                 auto const& prop_value = property_record.value();
-
-                if (StartsWith(prop_name, staged_prefix)) {
-                  has_staged_prop = true;
-                  auto actual_prop_name = prop_name.substr(staged_prefix.size());
-                  InitPropertySet(actual_prop_name, prop_value);
-                } else {
-                  InitPropertySet(prop_name, prop_value);
-                }
-            }
-
-            // Update persist prop file if there are staged props
-            if (has_staged_prop) {
-                PersistentProperties props = LoadPersistentPropertiesFromMemory();
-                // write current updated persist prop file
-                auto result = WritePersistentPropertyFile(props);
-                if (!result.ok()) {
-                    LOG(ERROR) << "Could not store persistent property: " << result.error();
-                }
+                InitPropertySet(prop_name, prop_value);
             }
 
             // Apply debug ramdisk special settings after persistent properties are loaded.
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 3351c4c..5757922 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -563,7 +563,7 @@
         }
     }
     if (timeout > 0ms) {
-        WaitToBeReaped(pids, timeout);
+        WaitToBeReaped(sigchld_fd, pids, timeout);
     } else {
         // Even if we don't to wait for services to stop, we still optimistically reap zombies.
         ReapAnyOutstandingChildren();
diff --git a/init/service.cpp b/init/service.cpp
index 5e900ee..311a132 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -136,6 +136,7 @@
 
 unsigned long Service::next_start_order_ = 1;
 bool Service::is_exec_service_running_ = false;
+int Service::sigchld_fd_ = -1;
 
 Service::Service(const std::string& name, Subcontext* subcontext_for_restart_commands,
                  const std::string& filename, const std::vector<std::string>& args)
@@ -194,7 +195,7 @@
     }
 }
 
-void Service::KillProcessGroup(int signal, bool report_oneshot) {
+void Service::KillProcessGroup(int signal) {
     // If we've already seen a successful result from killProcessGroup*(), then we have removed
     // the cgroup already and calling these functions a second time will simply result in an error.
     // This is true regardless of which signal was sent.
@@ -202,20 +203,11 @@
     if (!process_cgroup_empty_) {
         LOG(INFO) << "Sending signal " << signal << " to service '" << name_ << "' (pid " << pid_
                   << ") process group...";
-        int max_processes = 0;
         int r;
         if (signal == SIGTERM) {
-            r = killProcessGroupOnce(uid(), pid_, signal, &max_processes);
+            r = killProcessGroupOnce(uid(), pid_, signal);
         } else {
-            r = killProcessGroup(uid(), pid_, signal, &max_processes);
-        }
-
-        if (report_oneshot && max_processes > 0) {
-            LOG(WARNING)
-                    << "Killed " << max_processes
-                    << " additional processes from a oneshot process group for service '" << name_
-                    << "'. This is new behavior, previously child processes would not be killed in "
-                       "this case.";
+            r = killProcessGroup(uid(), pid_, signal);
         }
 
         if (r == 0) process_cgroup_empty_ = true;
@@ -265,7 +257,7 @@
 
 void Service::Reap(const siginfo_t& siginfo) {
     if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
-        KillProcessGroup(SIGKILL, false);
+        KillProcessGroup(SIGKILL);
     } else {
         // Legacy behavior from ~2007 until Android R: this else branch did not exist and we did not
         // kill the process group in this case.
@@ -273,7 +265,7 @@
             // The new behavior in Android R is to kill these process groups in all cases.  The
             // 'true' parameter instructions KillProcessGroup() to report a warning message where it
             // detects a difference in behavior has occurred.
-            KillProcessGroup(SIGKILL, true);
+            KillProcessGroup(SIGKILL);
         }
     }
 
diff --git a/init/service.h b/init/service.h
index 9f09cef..13c8b5f 100644
--- a/init/service.h
+++ b/init/service.h
@@ -156,11 +156,12 @@
     const Subcontext* subcontext() const { return subcontext_; }
     const std::string& filename() const { return filename_; }
     void set_filename(const std::string& name) { filename_ = name; }
+    static void SetSigchldFd(int sigchld_fd) { sigchld_fd_ = sigchld_fd; }
 
   private:
     void NotifyStateChange(const std::string& new_state) const;
     void StopOrReset(int how);
-    void KillProcessGroup(int signal, bool report_oneshot = false);
+    void KillProcessGroup(int signal);
     void SetProcessAttributesAndCaps(InterprocessFifo setsid_finished);
     void ResetFlagsForStart();
     Result<void> CheckConsole();
@@ -168,8 +169,10 @@
     void RunService(const std::vector<Descriptor>& descriptors, InterprocessFifo cgroups_activated,
                     InterprocessFifo setsid_finished);
     void SetMountNamespace();
+
     static unsigned long next_start_order_;
     static bool is_exec_service_running_;
+    static int sigchld_fd_;
 
     const std::string name_;
     std::set<std::string> classnames_;
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index a1b2cc5..92e350b 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -179,8 +179,9 @@
     if (!ParseInt(args[1], &service_->proc_attr_.priority,
                   static_cast<int>(ANDROID_PRIORITY_HIGHEST),  // highest is negative
                   static_cast<int>(ANDROID_PRIORITY_LOWEST))) {
-        return Errorf("process priority value must be range {} - {}", ANDROID_PRIORITY_HIGHEST,
-                      ANDROID_PRIORITY_LOWEST);
+        return Errorf("process priority value must be range {} - {}",
+                      static_cast<int>(ANDROID_PRIORITY_HIGHEST),
+                      static_cast<int>(ANDROID_PRIORITY_LOWEST));
     }
     return {};
 }
diff --git a/init/sigchld_handler.cpp b/init/sigchld_handler.cpp
index f8c501f..9d4c7c8 100644
--- a/init/sigchld_handler.cpp
+++ b/init/sigchld_handler.cpp
@@ -18,6 +18,7 @@
 
 #include <signal.h>
 #include <string.h>
+#include <sys/signalfd.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -31,6 +32,7 @@
 
 #include <thread>
 
+#include "epoll.h"
 #include "init.h"
 #include "service.h"
 #include "service_list.h"
@@ -121,8 +123,23 @@
     }
 }
 
-void WaitToBeReaped(const std::vector<pid_t>& pids, std::chrono::milliseconds timeout) {
+static void DiscardSiginfo(int signal_fd) {
+    signalfd_siginfo siginfo;
+    ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo)));
+    if (bytes_read != sizeof(siginfo)) {
+        LOG(WARNING) << "Unexpected: " << __func__ << " read " << bytes_read << " bytes instead of "
+                     << sizeof(siginfo);
+    }
+}
+
+void WaitToBeReaped(int sigchld_fd, const std::vector<pid_t>& pids,
+                    std::chrono::milliseconds timeout) {
     Timer t;
+    Epoll epoll;
+    // The init process passes a valid sigchld_fd argument but unit tests do not.
+    if (sigchld_fd >= 0) {
+        epoll.RegisterHandler(sigchld_fd, [sigchld_fd]() { DiscardSiginfo(sigchld_fd); });
+    }
     std::vector<pid_t> alive_pids(pids.begin(), pids.end());
     while (!alive_pids.empty() && t.duration() < timeout) {
         pid_t pid;
@@ -135,14 +152,18 @@
         if (alive_pids.empty()) {
             break;
         }
-        std::this_thread::sleep_for(50ms);
+        if (sigchld_fd >= 0) {
+            epoll.Wait(std::max(timeout - t.duration(), 0ms));
+        } else {
+            std::this_thread::sleep_for(50ms);
+        }
     }
     LOG(INFO) << "Waiting for " << pids.size() << " pids to be reaped took " << t << " with "
               << alive_pids.size() << " of them still running";
-    for (pid_t pid : pids) {
+    for (pid_t pid : alive_pids) {
         std::string status = "(no-such-pid)";
         ReadFileToString(StringPrintf("/proc/%d/status", pid), &status);
-        LOG(INFO) << "Still running: " << pid << ' ' << status;
+        LOG(INFO) << "Still running: " << pid << '\n' << status;
     }
 }
 
diff --git a/init/sigchld_handler.h b/init/sigchld_handler.h
index fac1020..e07a7d6 100644
--- a/init/sigchld_handler.h
+++ b/init/sigchld_handler.h
@@ -25,7 +25,8 @@
 
 void ReapAnyOutstandingChildren();
 
-void WaitToBeReaped(const std::vector<pid_t>& pids, std::chrono::milliseconds timeout);
+void WaitToBeReaped(int sigchld_fd, const std::vector<pid_t>& pids,
+                    std::chrono::milliseconds timeout);
 
 }  // namespace init
 }  // namespace android
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index dbaeb93..a319c63 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -26,7 +26,8 @@
 
 __BEGIN_DECLS
 
-static constexpr const char* CGROUPV2_CONTROLLER_NAME = "cgroup2";
+static constexpr const char* CGROUPV2_HIERARCHY_NAME = "cgroup2";
+[[deprecated]] static constexpr const char* CGROUPV2_CONTROLLER_NAME = "cgroup2";
 
 bool CgroupsAvailable();
 bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path);
@@ -67,14 +68,11 @@
 // Return 0 and removes the cgroup if there are no longer any processes in it.
 // Returns -1 in the case of an error occurring or if there are processes still running
 // even after retrying for up to 200ms.
-// If max_processes is not nullptr, it returns the maximum number of processes seen in the cgroup
-// during the killing process.  Note that this can be 0 if all processes from the process group have
-// already been terminated.
-int killProcessGroup(uid_t uid, int initialPid, int signal, int* max_processes = nullptr);
+int killProcessGroup(uid_t uid, int initialPid, int signal);
 
 // Returns the same as killProcessGroup(), however it does not retry, which means
 // that it only returns 0 in the case that the cgroup exists and it contains no processes.
-int killProcessGroupOnce(uid_t uid, int initialPid, int signal, int* max_processes = nullptr);
+int killProcessGroupOnce(uid_t uid, int initialPid, int signal);
 
 // Sends the provided signal to all members of a process group, but does not wait for processes to
 // exit, or for the cgroup to be removed. Callers should also ensure that killProcessGroup is called
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index cc2565f..d352f0e 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -129,7 +129,7 @@
     }
 
     if (!attr->GetPathForTask(tid, path)) {
-        PLOG(ERROR) << "Failed to find cgroup for tid " << tid;
+        LOG(ERROR) << "Failed to find cgroup for tid " << tid;
         return false;
     }
 
@@ -213,7 +213,7 @@
     return StringPrintf("%s/uid_%u/pid_%d", cgroup, uid, pid);
 }
 
-static int RemoveProcessGroup(const char* cgroup, uid_t uid, int pid, unsigned int retries) {
+static int RemoveCgroup(const char* cgroup, uid_t uid, int pid, unsigned int retries) {
     int ret = 0;
     auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, pid);
 
@@ -233,7 +233,7 @@
     return ret;
 }
 
-static bool RemoveUidProcessGroups(const std::string& uid_path, bool empty_only) {
+static bool RemoveUidCgroups(const std::string& uid_path, bool empty_only) {
     std::unique_ptr<DIR, decltype(&closedir)> uid(opendir(uid_path.c_str()), closedir);
     bool empty = true;
     if (uid != NULL) {
@@ -275,11 +275,11 @@
     return empty;
 }
 
-void removeAllProcessGroupsInternal(bool empty_only) {
+static void removeAllProcessGroupsInternal(bool empty_only) {
     std::vector<std::string> cgroups;
     std::string path, memcg_apps_path;
 
-    if (CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &path)) {
+    if (CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &path)) {
         cgroups.push_back(path);
     }
     if (CgroupGetMemcgAppsPath(&memcg_apps_path) && memcg_apps_path != path) {
@@ -302,7 +302,7 @@
                 }
 
                 auto path = StringPrintf("%s/%s", cgroup_root_path.c_str(), dir->d_name);
-                if (!RemoveUidProcessGroups(path, empty_only)) {
+                if (!RemoveUidCgroups(path, empty_only)) {
                     LOG(VERBOSE) << "Skip removing " << path;
                     continue;
                 }
@@ -455,29 +455,21 @@
     return (!fd || feof(fd.get())) ? processes : -1;
 }
 
-static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries,
-                            int* max_processes) {
+static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries) {
     CHECK_GE(uid, 0);
     CHECK_GT(initialPid, 0);
 
     std::string hierarchy_root_path;
     if (CgroupsAvailable()) {
-        CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &hierarchy_root_path);
+        CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path);
     }
     const char* cgroup = hierarchy_root_path.c_str();
 
     std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
 
-    if (max_processes != nullptr) {
-        *max_processes = 0;
-    }
-
     int retry = retries;
     int processes;
     while ((processes = DoKillProcessGroupOnce(cgroup, uid, initialPid, signal)) > 0) {
-        if (max_processes != nullptr && processes > *max_processes) {
-            *max_processes = processes;
-        }
         LOG(VERBOSE) << "Killed " << processes << " processes for processgroup " << initialPid;
         if (!CgroupsAvailable()) {
             // makes no sense to retry, because there are no cgroup_procs file
@@ -519,12 +511,12 @@
         }
 
         // 400 retries correspond to 2 secs max timeout
-        int err = RemoveProcessGroup(cgroup, uid, initialPid, 400);
+        int err = RemoveCgroup(cgroup, uid, initialPid, 400);
 
         if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
             std::string memcg_apps_path;
             if (CgroupGetMemcgAppsPath(&memcg_apps_path) &&
-                RemoveProcessGroup(memcg_apps_path.c_str(), uid, initialPid, 400) < 0) {
+                RemoveCgroup(memcg_apps_path.c_str(), uid, initialPid, 400) < 0) {
                 return -1;
             }
         }
@@ -540,18 +532,18 @@
     }
 }
 
-int killProcessGroup(uid_t uid, int initialPid, int signal, int* max_processes) {
-    return KillProcessGroup(uid, initialPid, signal, 40 /*retries*/, max_processes);
+int killProcessGroup(uid_t uid, int initialPid, int signal) {
+    return KillProcessGroup(uid, initialPid, signal, 40 /*retries*/);
 }
 
-int killProcessGroupOnce(uid_t uid, int initialPid, int signal, int* max_processes) {
-    return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/, max_processes);
+int killProcessGroupOnce(uid_t uid, int initialPid, int signal) {
+    return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/);
 }
 
 int sendSignalToProcessGroup(uid_t uid, int initialPid, int signal) {
     std::string hierarchy_root_path;
     if (CgroupsAvailable()) {
-        CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &hierarchy_root_path);
+        CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &hierarchy_root_path);
     }
     const char* cgroup = hierarchy_root_path.c_str();
     return DoKillProcessGroupOnce(cgroup, uid, initialPid, signal);
@@ -609,7 +601,7 @@
     CHECK_GT(initialPid, 0);
 
     if (memControl && !UsePerAppMemcg()) {
-        PLOG(ERROR) << "service memory controls are used without per-process memory cgroup support";
+        LOG(ERROR) << "service memory controls are used without per-process memory cgroup support";
         return -EINVAL;
     }
 
@@ -625,19 +617,19 @@
     }
 
     std::string cgroup;
-    CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cgroup);
+    CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, &cgroup);
     return createProcessGroupInternal(uid, initialPid, cgroup, true);
 }
 
 static bool SetProcessGroupValue(int tid, const std::string& attr_name, int64_t value) {
     if (!isMemoryCgroupSupported()) {
-        PLOG(ERROR) << "Memcg is not mounted.";
+        LOG(ERROR) << "Memcg is not mounted.";
         return false;
     }
 
     std::string path;
     if (!CgroupGetAttributePathForTask(attr_name, tid, &path)) {
-        PLOG(ERROR) << "Failed to find attribute '" << attr_name << "'";
+        LOG(ERROR) << "Failed to find attribute '" << attr_name << "'";
         return false;
     }
 
@@ -672,4 +664,4 @@
     }
 
     return tp->IsValidForProcess(uid, pid);
-}
\ No newline at end of file
+}
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index fbeedf9..4e44c91 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -212,7 +212,7 @@
     if (root.isMember("Cgroups2")) {
         const Json::Value& cgroups2 = root["Cgroups2"];
         std::string root_path = cgroups2["Path"].asString();
-        MergeCgroupToDescriptors(descriptors, cgroups2, CGROUPV2_CONTROLLER_NAME, "", 2);
+        MergeCgroupToDescriptors(descriptors, cgroups2, CGROUPV2_HIERARCHY_NAME, "", 2);
 
         const Json::Value& childGroups = cgroups2["Controllers"];
         for (Json::Value::ArrayIndex i = 0; i < childGroups.size(); ++i) {
@@ -358,7 +358,7 @@
     const format::CgroupController* controller = descriptor.controller();
 
     if (controller->version() == 2) {
-        if (!strcmp(controller->name(), CGROUPV2_CONTROLLER_NAME)) {
+        if (!strcmp(controller->name(), CGROUPV2_HIERARCHY_NAME)) {
             return MountV2CgroupController(descriptor);
         } else {
             return ActivateV2CgroupController(descriptor);
diff --git a/libprocessgroup/task_profiles_test.cpp b/libprocessgroup/task_profiles_test.cpp
index 99d819a..b17e695 100644
--- a/libprocessgroup/task_profiles_test.cpp
+++ b/libprocessgroup/task_profiles_test.cpp
@@ -116,7 +116,7 @@
     }
     bool GetPathForTask(int tid, std::string* path) const override {
 #ifdef __ANDROID__
-        CHECK(CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, path));
+        CHECK(CgroupGetControllerPath(CGROUPV2_HIERARCHY_NAME, path));
         CHECK_GT(path->length(), 0);
         if (path->rbegin()[0] != '/') {
             *path += "/";
diff --git a/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h b/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h
index 0548021..65705ac 100644
--- a/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h
+++ b/property_service/libpropertyinfoparser/include/property_info_parser/property_info_parser.h
@@ -20,6 +20,8 @@
 #include <stdint.h>
 #include <stdlib.h>
 
+static constexpr char PROP_TREE_FILE[] = "/dev/__properties__/property_info";
+
 namespace android {
 namespace properties {