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

Commit 2d2fd725 authored by Daniel Rosenberg's avatar Daniel Rosenberg
Browse files

Switch up Cow Format to be resumable

This switches up the format to alternate ops with data, followed by a
footer containing additional meta information. This allows the file to
be resumed at arbitrary points if writing gets interrupted by power
loss.
Also adds a label op, which allows labeling future ops as connected.
If the footer is missing, Append will treat the last label as possibly
corrupt, and ignore it.

Change-Id: I126e15837d710776f9396e7afc9b0cd595e26b59
Bug: 168829493
Test: cow_api_test
parent b4a81ccd
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -149,6 +149,7 @@ cc_defaults {
        "cow_decompress.cpp",
        "cow_reader.cpp",
        "cow_writer.cpp",
        "cow_format.cpp",
    ],
}

+137 −3
Original line number Diff line number Diff line
@@ -76,13 +76,15 @@ TEST_F(CowTest, ReadWrite) {

    CowReader reader;
    CowHeader header;
    CowFooter footer;
    ASSERT_TRUE(reader.Parse(cow_->fd));
    ASSERT_TRUE(reader.GetHeader(&header));
    ASSERT_TRUE(reader.GetFooter(&footer));
    ASSERT_EQ(header.magic, kCowMagicNumber);
    ASSERT_EQ(header.major_version, kCowVersionMajor);
    ASSERT_EQ(header.minor_version, kCowVersionMinor);
    ASSERT_EQ(header.block_size, options.block_size);
    ASSERT_EQ(header.num_ops, 4);
    ASSERT_EQ(footer.op.num_ops, 4);

    auto iter = reader.GetOpIter();
    ASSERT_NE(iter, nullptr);
@@ -105,7 +107,6 @@ TEST_F(CowTest, ReadWrite) {
    ASSERT_EQ(op->compression, kCowCompressNone);
    ASSERT_EQ(op->data_length, 4096);
    ASSERT_EQ(op->new_block, 50);
    ASSERT_EQ(op->source, 106);
    ASSERT_TRUE(reader.ReadData(*op, &sink));
    ASSERT_EQ(sink.stream(), data);

@@ -163,7 +164,6 @@ TEST_F(CowTest, CompressGz) {
    ASSERT_EQ(op->compression, kCowCompressGz);
    ASSERT_EQ(op->data_length, 56);  // compressed!
    ASSERT_EQ(op->new_block, 50);
    ASSERT_EQ(op->source, 106);
    ASSERT_TRUE(reader.ReadData(*op, &sink));
    ASSERT_EQ(sink.stream(), data);

@@ -272,6 +272,7 @@ TEST_F(CowTest, GetSize) {
}

TEST_F(CowTest, Append) {
    cow_->DoNotRemove();
    CowOptions options;
    auto writer = std::make_unique<CowWriter>(options);
    ASSERT_TRUE(writer->Initialize(cow_->fd));
@@ -325,6 +326,139 @@ TEST_F(CowTest, Append) {
    ASSERT_TRUE(iter->Done());
}

TEST_F(CowTest, AppendCorrupted) {
    CowOptions options;
    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->AddLabel(0));
    ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
    ASSERT_TRUE(writer->AddLabel(1));
    ASSERT_TRUE(writer->AddZeroBlocks(50, 1));
    ASSERT_TRUE(writer->Finalize());
    // Drop the tail end of the header. Last entry may be corrupted.
    ftruncate(cow_->fd, writer->GetCowSize() - 5);

    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);

    writer = std::make_unique<CowWriter>(options);
    ASSERT_TRUE(writer->Initialize(cow_->fd, CowWriter::OpenMode::APPEND));

    ASSERT_TRUE(writer->AddLabel(2));
    ASSERT_TRUE(writer->AddZeroBlocks(50, 1));

    std::string data2 = "More data!";
    data2.resize(options.block_size, '\0');
    ASSERT_TRUE(writer->AddLabel(3));
    ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size()));
    ASSERT_TRUE(writer->Finalize());

    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 all three operations.
    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, kCowLabelOp);
    ASSERT_EQ(op->source, 0);

    iter->Next();

    ASSERT_FALSE(iter->Done());
    op = &iter->Get();
    ASSERT_EQ(op->type, kCowReplaceOp);
    ASSERT_TRUE(reader.ReadData(*op, &sink));
    ASSERT_EQ(sink.stream(), data);

    iter->Next();
    sink.Reset();

    ASSERT_FALSE(iter->Done());
    op = &iter->Get();
    ASSERT_EQ(op->type, kCowLabelOp);
    ASSERT_EQ(op->source, 2);

    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, 3);

    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_TRUE(iter->Done());
}

TEST_F(CowTest, AppendExtendedCorrupted) {
    CowOptions options;
    auto writer = std::make_unique<CowWriter>(options);
    ASSERT_TRUE(writer->Initialize(cow_->fd));

    ASSERT_TRUE(writer->AddLabel(5));
    ASSERT_TRUE(writer->AddLabel(6));

    std::string data = "This is some data, believe it";
    data.resize(options.block_size * 2, '\0');
    ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));

    // fail to write the footer

    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);

    writer = std::make_unique<CowWriter>(options);
    ASSERT_TRUE(writer->Initialize(cow_->fd, CowWriter::OpenMode::APPEND));

    ASSERT_TRUE(writer->Finalize());

    struct stat buf;
    ASSERT_EQ(fstat(cow_->fd, &buf), 0);
    ASSERT_EQ(buf.st_size, writer->GetCowSize());

    // Read back all three operations.
    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, kCowLabelOp);
    ASSERT_EQ(op->source, 5);

    iter->Next();
    ASSERT_TRUE(iter->Done());
}

}  // namespace snapshot
}  // namespace android

+63 −0
Original line number Diff line number Diff line
//
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include <libsnapshot/cow_format.h>
#include <sys/types.h>
#include <unistd.h>

#include <android-base/logging.h>

namespace android {
namespace snapshot {

std::ostream& operator<<(std::ostream& os, CowOperation const& op) {
    os << "CowOperation(type:";
    if (op.type == kCowCopyOp)
        os << "kCowCopyOp,    ";
    else if (op.type == kCowReplaceOp)
        os << "kCowReplaceOp, ";
    else if (op.type == kCowZeroOp)
        os << "kZeroOp,       ";
    else if (op.type == kCowFooterOp)
        os << "kCowFooterOp,  ";
    else if (op.type == kCowLabelOp)
        os << "kCowLabelOp,   ";
    else
        os << (int)op.type << "?,";
    os << "compression:";
    if (op.compression == kCowCompressNone)
        os << "kCowCompressNone,   ";
    else if (op.compression == kCowCompressGz)
        os << "kCowCompressGz,     ";
    else if (op.compression == kCowCompressBrotli)
        os << "kCowCompressBrotli, ";
    else
        os << (int)op.compression << "?, ";
    os << "data_length:" << op.data_length << ",\t";
    os << "new_block:" << op.new_block << ",\t";
    os << "source:" << op.source << ")";
    return os;
}

int64_t GetNextOpOffset(const CowOperation& op) {
    if (op.type == kCowReplaceOp)
        return op.data_length;
    else
        return 0;
}

}  // namespace snapshot
}  // namespace android
+60 −52
Original line number Diff line number Diff line
@@ -18,17 +18,19 @@
#include <unistd.h>

#include <limits>
#include <vector>

#include <android-base/file.h>
#include <android-base/logging.h>
#include <libsnapshot/cow_reader.h>
#include <zlib.h>

#include "cow_decompress.h"

namespace android {
namespace snapshot {

CowReader::CowReader() : fd_(-1), header_(), fd_size_(0) {}
CowReader::CowReader() : fd_(-1), header_(), footer_(), fd_size_(0), has_footer_(false) {}

static void SHA256(const void*, size_t, uint8_t[]) {
#if 0
@@ -63,16 +65,6 @@ bool CowReader::Parse(android::base::borrowed_fd fd) {
        return false;
    }

    // Validity check the ops range.
    if (header_.ops_offset >= fd_size_) {
        LOG(ERROR) << "ops offset " << header_.ops_offset << " larger than fd size " << fd_size_;
        return false;
    }
    if (fd_size_ - header_.ops_offset < header_.ops_size) {
        LOG(ERROR) << "ops size " << header_.ops_size << " is too large";
        return false;
    }

    if (header_.magic != kCowMagicNumber) {
        LOG(ERROR) << "Header Magic corrupted. Magic: " << header_.magic
                   << "Expected: " << kCowMagicNumber;
@@ -83,6 +75,11 @@ bool CowReader::Parse(android::base::borrowed_fd fd) {
                   << sizeof(CowHeader);
        return false;
    }
    if (header_.footer_size != sizeof(CowFooter)) {
        LOG(ERROR) << "Footer size unknown, read " << header_.footer_size << ", expected "
                   << sizeof(CowFooter);
        return false;
    }

    if ((header_.major_version != kCowVersionMajor) ||
        (header_.minor_version != kCowVersionMinor)) {
@@ -94,19 +91,16 @@ bool CowReader::Parse(android::base::borrowed_fd fd) {
        return false;
    }

    uint8_t header_csum[32];
    {
        CowHeader tmp = header_;
        memset(&tmp.header_checksum, 0, sizeof(tmp.header_checksum));
        memset(header_csum, 0, sizeof(uint8_t) * 32);

        SHA256(&tmp, sizeof(tmp), header_csum);
    auto footer_pos = lseek(fd_.get(), -header_.footer_size, SEEK_END);
    if (footer_pos != fd_size_ - header_.footer_size) {
        LOG(ERROR) << "Failed to read full footer!";
        return false;
    }
    if (memcmp(header_csum, header_.header_checksum, sizeof(header_csum)) != 0) {
        LOG(ERROR) << "header checksum is invalid";
    if (!android::base::ReadFully(fd_, &footer_, sizeof(footer_))) {
        PLOG(ERROR) << "read footer failed";
        return false;
    }

    has_footer_ = (footer_.op.type == kCowFooterOp);
    return true;
}

@@ -115,74 +109,88 @@ bool CowReader::GetHeader(CowHeader* header) {
    return true;
}

bool CowReader::GetFooter(CowFooter* footer) {
    if (!has_footer_) return false;
    *footer = footer_;
    return true;
}

class CowOpIter final : public ICowOpIter {
  public:
    CowOpIter(std::unique_ptr<uint8_t[]>&& ops, size_t len);
    CowOpIter(std::unique_ptr<std::vector<CowOperation>>&& ops);

    bool Done() override;
    const CowOperation& Get() override;
    void Next() override;

  private:
    bool HasNext();

    std::unique_ptr<uint8_t[]> ops_;
    const uint8_t* pos_;
    const uint8_t* end_;
    bool done_;
    std::unique_ptr<std::vector<CowOperation>> ops_;
    std::vector<CowOperation>::iterator op_iter_;
};

CowOpIter::CowOpIter(std::unique_ptr<uint8_t[]>&& ops, size_t len)
    : ops_(std::move(ops)), pos_(ops_.get()), end_(pos_ + len), done_(!HasNext()) {}

bool CowOpIter::Done() {
    return done_;
CowOpIter::CowOpIter(std::unique_ptr<std::vector<CowOperation>>&& ops) {
    ops_ = std::move(ops);
    op_iter_ = ops_.get()->begin();
}

bool CowOpIter::HasNext() {
    return pos_ < end_ && size_t(end_ - pos_) >= sizeof(CowOperation);
bool CowOpIter::Done() {
    return op_iter_ == ops_.get()->end();
}

void CowOpIter::Next() {
    CHECK(!Done());

    pos_ += sizeof(CowOperation);
    if (!HasNext()) done_ = true;
    op_iter_++;
}

const CowOperation& CowOpIter::Get() {
    CHECK(!Done());
    CHECK(HasNext());
    return *reinterpret_cast<const CowOperation*>(pos_);
    return (*op_iter_);
}

std::unique_ptr<ICowOpIter> CowReader::GetOpIter() {
    if (lseek(fd_.get(), header_.ops_offset, SEEK_SET) < 0) {
    uint64_t pos = lseek(fd_.get(), sizeof(header_), SEEK_SET);
    if (pos != sizeof(header_)) {
        PLOG(ERROR) << "lseek ops failed";
        return nullptr;
    }
    auto ops_buffer = std::make_unique<uint8_t[]>(header_.ops_size);
    if (!android::base::ReadFully(fd_, ops_buffer.get(), header_.ops_size)) {
        PLOG(ERROR) << "read ops failed";
    auto ops_buffer = std::make_unique<std::vector<CowOperation>>();
    if (has_footer_) ops_buffer->reserve(footer_.op.num_ops);
    uint64_t current_op_num = 0;
    uint64_t last_pos = fd_size_ - (has_footer_ ? sizeof(footer_) : sizeof(CowOperation));

    // Alternating op and data
    while (pos < last_pos) {
        ops_buffer->resize(current_op_num + 1);
        if (!android::base::ReadFully(fd_, ops_buffer->data() + current_op_num,
                                      sizeof(CowOperation))) {
            PLOG(ERROR) << "read op failed";
            return nullptr;
        }
        auto current_op = ops_buffer->data()[current_op_num];
        pos = lseek(fd_.get(), GetNextOpOffset(current_op), SEEK_CUR);
        current_op_num++;
    }

    uint8_t csum[32];
    memset(csum, 0, sizeof(uint8_t) * 32);

    SHA256(ops_buffer.get(), header_.ops_size, csum);
    if (memcmp(csum, header_.ops_checksum, sizeof(csum)) != 0) {
    if (has_footer_) {
        SHA256(ops_buffer.get()->data(), footer_.op.ops_size, csum);
        if (memcmp(csum, footer_.data.ops_checksum, sizeof(csum)) != 0) {
            LOG(ERROR) << "ops checksum does not match";
            return nullptr;
        }
    } else {
        LOG(INFO) << "No Footer, recovered data";
    }

    return std::make_unique<CowOpIter>(std::move(ops_buffer), header_.ops_size);
    return std::make_unique<CowOpIter>(std::move(ops_buffer));
}

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 < sizeof(header_) || offset >= header_.ops_offset || len >= fd_size_ ||
        offset + len > header_.ops_offset) {
    if (offset < sizeof(header_) || offset >= fd_size_ - sizeof(footer_) || len >= fd_size_ ||
        offset + len > fd_size_ - sizeof(footer_)) {
        LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes";
        return false;
    }
+83 −50
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
#include <unistd.h>

#include <limits>
#include <queue>

#include <android-base/file.h>
#include <android-base/logging.h>
@@ -65,6 +66,10 @@ bool ICowWriter::AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
    return EmitZeroBlocks(new_block_start, num_blocks);
}

bool ICowWriter::AddLabel(uint64_t label) {
    return EmitLabel(label);
}

bool ICowWriter::ValidateNewBlock(uint64_t new_block) {
    if (options_.max_blocks && new_block >= options_.max_blocks.value()) {
        LOG(ERROR) << "New block " << new_block << " exceeds maximum block count "
@@ -84,7 +89,11 @@ void CowWriter::SetupHeaders() {
    header_.major_version = kCowVersionMajor;
    header_.minor_version = kCowVersionMinor;
    header_.header_size = sizeof(CowHeader);
    header_.footer_size = sizeof(CowFooter);
    header_.block_size = options_.block_size;
    footer_ = {};
    footer_.op.data_length = 64;
    footer_.op.type = kCowFooterOp;
}

bool CowWriter::ParseOptions() {
@@ -143,33 +152,53 @@ bool CowWriter::OpenForWrite() {
        return false;
    }

    header_.ops_offset = header_.header_size;
    next_op_pos_ = sizeof(header_);
    return true;
}

bool CowWriter::OpenForAppend() {
    auto reader = std::make_unique<CowReader>();
    bool incomplete = false;
    std::queue<CowOperation> toAdd;
    if (!reader->Parse(fd_) || !reader->GetHeader(&header_)) {
        return false;
    }
    incomplete = !reader->GetFooter(&footer_);

    options_.block_size = header_.block_size;

    // Reset this, since we're going to reimport all operations.
    header_.num_ops = 0;
    footer_.op.num_ops = 0;
    next_op_pos_ = sizeof(header_);

    auto iter = reader->GetOpIter();
    while (!iter->Done()) {
        auto& op = iter->Get();
        CowOperation op = iter->Get();
        if (op.type == kCowFooterOp) break;
        if (incomplete) {
            // Last operation translation may be corrupt. Wait to add it.
            if (op.type == kCowLabelOp) {
                while (!toAdd.empty()) {
                    AddOperation(toAdd.front());
                    toAdd.pop();
                }
            }
            toAdd.push(op);
        } else {
            AddOperation(op);

        }
        iter->Next();
    }

    // Free reader so we own the descriptor position again.
    reader = nullptr;

    // Seek to the end of the data section.
    if (lseek(fd_.get(), header_.ops_offset, SEEK_SET) < 0) {
    // Position for new writing
    if (ftruncate(fd_.get(), next_op_pos_) != 0) {
        PLOG(ERROR) << "Failed to trim file";
        return false;
    }
    if (lseek(fd_.get(), 0, SEEK_END) < 0) {
        PLOG(ERROR) << "lseek failed";
        return false;
    }
@@ -181,22 +210,18 @@ bool CowWriter::EmitCopy(uint64_t new_block, uint64_t old_block) {
    op.type = kCowCopyOp;
    op.new_block = new_block;
    op.source = old_block;
    AddOperation(op);
    return true;
    return WriteOperation(op);
}

bool CowWriter::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
    uint64_t pos;
    if (!GetDataPos(&pos)) {
        return false;
    }

    const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
    uint64_t pos;
    for (size_t i = 0; i < size / header_.block_size; i++) {
        CowOperation op = {};
        op.type = kCowReplaceOp;
        op.new_block = new_block_start + i;
        op.source = pos;
        GetDataPos(&pos);
        op.source = pos + sizeof(op);

        if (compression_) {
            auto data = Compress(iter, header_.block_size);
@@ -208,25 +233,22 @@ bool CowWriter::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t
                LOG(ERROR) << "Compressed block is too large: " << data.size() << " bytes";
                return false;
            }
            if (!WriteRawData(data.data(), data.size())) {
            op.compression = compression_;
            op.data_length = static_cast<uint16_t>(data.size());

            if (!WriteOperation(op, data.data(), data.size())) {
                PLOG(ERROR) << "AddRawBlocks: write failed";
                return false;
            }
            op.compression = compression_;
            op.data_length = static_cast<uint16_t>(data.size());
            pos += data.size();
        } else {
            op.data_length = static_cast<uint16_t>(header_.block_size);
            pos += header_.block_size;
            if (!WriteOperation(op, iter, header_.block_size)) {
                PLOG(ERROR) << "AddRawBlocks: write failed";
                return false;
            }

        AddOperation(op);
        iter += header_.block_size;
        }

    if (!compression_ && !WriteRawData(data, size)) {
        PLOG(ERROR) << "AddRawBlocks: write failed";
        return false;
        iter += header_.block_size;
    }
    return true;
}
@@ -237,11 +259,18 @@ bool CowWriter::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
        op.type = kCowZeroOp;
        op.new_block = new_block_start + i;
        op.source = 0;
        AddOperation(op);
        WriteOperation(op);
    }
    return true;
}

bool CowWriter::EmitLabel(uint64_t label) {
    CowOperation op = {};
    op.type = kCowLabelOp;
    op.source = label;
    return WriteOperation(op);
}

std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) {
    switch (compression_) {
        case kCowCompressGz: {
@@ -294,33 +323,27 @@ static void SHA256(const void*, size_t, uint8_t[]) {
}

bool CowWriter::Finalize() {
    header_.ops_size = ops_.size();

    memset(header_.ops_checksum, 0, sizeof(uint8_t) * 32);
    memset(header_.header_checksum, 0, sizeof(uint8_t) * 32);

    SHA256(ops_.data(), ops_.size(), header_.ops_checksum);
    SHA256(&header_, sizeof(header_), header_.header_checksum);
    footer_.op.ops_size = ops_.size() + sizeof(footer_.op);
    uint64_t pos;

    if (lseek(fd_.get(), 0, SEEK_SET)) {
        PLOG(ERROR) << "lseek failed";
        return false;
    }
    if (!android::base::WriteFully(fd_, &header_, sizeof(header_))) {
        PLOG(ERROR) << "write header failed";
        return false;
    }
    if (lseek(fd_.get(), header_.ops_offset, SEEK_SET) < 0) {
        PLOG(ERROR) << "lseek ops failed";
    if (!GetDataPos(&pos)) {
        PLOG(ERROR) << "failed to get file position";
        return false;
    }
    if (!WriteFully(fd_, ops_.data(), ops_.size())) {
        PLOG(ERROR) << "write ops failed";
    memset(&footer_.data.ops_checksum, 0, sizeof(uint8_t) * 32);
    memset(&footer_.data.footer_checksum, 0, sizeof(uint8_t) * 32);

    SHA256(ops_.data(), ops_.size(), footer_.data.ops_checksum);
    SHA256(&footer_.op, sizeof(footer_.op), footer_.data.footer_checksum);
    // Write out footer at end of file
    if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&footer_),
                                   sizeof(footer_))) {
        PLOG(ERROR) << "write footer failed";
        return false;
    }

    // Re-position for any subsequent writes.
    if (lseek(fd_.get(), header_.ops_offset, SEEK_SET) < 0) {
    if (lseek(fd_.get(), pos, SEEK_SET) < 0) {
        PLOG(ERROR) << "lseek ops failed";
        return false;
    }
@@ -328,7 +351,7 @@ bool CowWriter::Finalize() {
}

uint64_t CowWriter::GetCowSize() {
    return header_.ops_offset + header_.num_ops * sizeof(CowOperation);
    return next_op_pos_ + sizeof(footer_);
}

bool CowWriter::GetDataPos(uint64_t* pos) {
@@ -341,8 +364,19 @@ bool CowWriter::GetDataPos(uint64_t* pos) {
    return true;
}

bool CowWriter::WriteOperation(const CowOperation& op, const void* data, size_t size) {
    if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&op), sizeof(op))) {
        return false;
    }
    if (data != NULL && size > 0)
        if (!WriteRawData(data, size)) return false;
    AddOperation(op);
    return !fsync(fd_.get());
}

void CowWriter::AddOperation(const CowOperation& op) {
    header_.num_ops++;
    footer_.op.num_ops++;
    next_op_pos_ += sizeof(CowOperation) + GetNextOpOffset(op);
    ops_.insert(ops_.size(), reinterpret_cast<const uint8_t*>(&op), sizeof(op));
}

@@ -350,7 +384,6 @@ bool CowWriter::WriteRawData(const void* data, size_t size) {
    if (!android::base::WriteFully(fd_, data, size)) {
        return false;
    }
    header_.ops_offset += size;
    return true;
}

Loading