Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2e67e777 authored by Daniel Rosenberg's avatar Daniel Rosenberg Committed by Gerrit Code Review
Browse files

Merge changes Ia3aa8b3b,I6e8b80a8,I28552889

* changes:
  libsnapshot: Support cluster_ops in make_cow_from_ab_ota
  libsnapshot: Add silent option to inspect_cow
  libsnapshot: Group CowOperations into clusters
parents a9e6057d 8603df26
Loading
Loading
Loading
Loading
+159 −0
Original line number Diff line number Diff line
@@ -525,6 +525,165 @@ TEST_F(CowTest, AppendbyLabel) {
    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

+31 −3
Original line number Diff line number Diff line
@@ -35,6 +35,10 @@ std::ostream& operator<<(std::ostream& os, CowOperation const& op) {
        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,12 +56,36 @@ std::ostream& operator<<(std::ostream& os, CowOperation const& op) {
    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
}  // namespace android
+69 −34
Original line number Diff line number Diff line
@@ -81,6 +81,24 @@ bool CowReader::Parse(android::base::borrowed_fd fd, std::optional<uint64_t> lab
                   << 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,47 +121,66 @@ bool CowReader::ParseOps(std::optional<uint64_t> label) {
    }

    auto ops_buffer = std::make_shared<std::vector<CowOperation>>();

    // Alternating op and data
    while (true) {
        ops_buffer->emplace_back();
        if (!android::base::ReadFully(fd_, &ops_buffer->back(), sizeof(CowOperation))) {
    uint64_t current_op_num = 0;
    uint64_t cluster_ops = header_.cluster_ops ?: 1;
    bool done = false;

    // 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) {
            PLOG(ERROR) << "lseek next op failed";
            return false;
        }
        pos = static_cast<uint64_t>(offs);

        if (current_op.type == kCowLabelOp) {
            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.
            ops_buffer->pop_back();
                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;
        }
        ops_buffer->resize(current_op_num);
    }

    // To successfully parse a COW file, we need either:
    //  (1) a label to read up to, and for that label to be found, or
    //  (2) a valid footer.
@@ -198,9 +235,7 @@ void CowReader::InitializeMerge() {

    // Remove all the metadata operations
    ops_->erase(std::remove_if(ops_.get()->begin(), ops_.get()->end(),
                               [](CowOperation& op) {
                                   return (op.type == kCowFooterOp || op.type == kCowLabelOp);
                               }),
                               [](CowOperation& op) { return IsMetadataOp(op); }),
                ops_.get()->end());

    // We will re-arrange the vector in such a way that
+86 −16
Original line number Diff line number Diff line
@@ -90,8 +90,10 @@ void CowWriter::SetupHeaders() {
    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 @@ bool CowWriter::ParseOptions() {
        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 @@ bool CowWriter::InitializeAppend(android::base::borrowed_fd fd, uint64_t label)
    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 @@ bool CowWriter::OpenForWrite() {
        return false;
    }

    next_op_pos_ = sizeof(header_);
    InitPos();
    return true;
}

@@ -197,13 +216,14 @@ bool CowWriter::OpenForAppend(uint64_t label) {
    }

    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::EmitCopy(uint64_t new_block, uint64_t old_block) {

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 @@ bool CowWriter::EmitLabel(uint64_t label) {
    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 @@ static void SHA256(const void*, size_t, uint8_t[]) {
}

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;

    // 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;
    }

    if (!GetDataPos(&pos)) {
        PLOG(ERROR) << "failed to get file position";
    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,17 +402,25 @@ bool CowWriter::Finalize() {
        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() {
    if (current_data_size_ > 0) {
        return next_data_pos_ + sizeof(footer_);
    } else {
        return next_op_pos_ + sizeof(footer_);
    }
}

bool CowWriter::GetDataPos(uint64_t* pos) {
    off_t offs = lseek(fd_.get(), 0, SEEK_CUR);
@@ -387,6 +433,15 @@ bool CowWriter::GetDataPos(uint64_t* pos) {
}

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 @@ bool CowWriter::WriteOperation(const CowOperation& op, const void* data, size_t

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;
    }
+14 −2
Original line number Diff line number Diff line
@@ -57,9 +57,15 @@ struct CowHeader {
    // 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 @@ struct CowOperation {
    // 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 kCowCopyOp = 1;
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 @@ struct CowFooter {

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
Loading