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
This commit is contained in:
Daniel Rosenberg 2020-12-04 03:47:57 +00:00 committed by Gerrit Code Review
commit 2e67e77752
10 changed files with 409 additions and 68 deletions

View file

@ -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

View file

@ -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,11 +56,35 @@ 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

View file

@ -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,45 +121,64 @@ bool CowReader::ParseOps(std::optional<uint64_t> label) {
}
auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
uint64_t current_op_num = 0;
uint64_t cluster_ops = header_.cluster_ops ?: 1;
bool done = false;
// Alternating op and data
while (true) {
ops_buffer->emplace_back();
if (!android::base::ReadFully(fd_, &ops_buffer->back(), sizeof(CowOperation))) {
// Alternating op clusters and data
while (!done) {
uint64_t to_add = std::min(cluster_ops, (fd_size_ - pos) / sizeof(CowOperation));
if (to_add == 0) break;
ops_buffer->resize(current_op_num + to_add);
if (!android::base::ReadFully(fd_, &ops_buffer->data()[current_op_num],
to_add * sizeof(CowOperation))) {
PLOG(ERROR) << "read op failed";
return false;
}
// Parse current cluster to find start of next cluster
while (current_op_num < ops_buffer->size()) {
auto& current_op = ops_buffer->data()[current_op_num];
current_op_num++;
pos += sizeof(CowOperation) + GetNextOpOffset(current_op, header_.cluster_ops);
auto& current_op = ops_buffer->back();
off_t offs = lseek(fd_.get(), GetNextOpOffset(current_op), SEEK_CUR);
if (offs < 0) {
if (current_op.type == kCowClusterOp) {
break;
} else if (current_op.type == kCowLabelOp) {
last_label_ = {current_op.source};
// If we reach the requested label, stop reading.
if (label && label.value() == current_op.source) {
done = true;
break;
}
} else if (current_op.type == kCowFooterOp) {
footer_.emplace();
CowFooter* footer = &footer_.value();
memcpy(&footer_->op, &current_op, sizeof(footer->op));
off_t offs = lseek(fd_.get(), pos, SEEK_SET);
if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
PLOG(ERROR) << "lseek next op failed";
return false;
}
if (!android::base::ReadFully(fd_, &footer->data, sizeof(footer->data))) {
LOG(ERROR) << "Could not read COW footer";
return false;
}
// Drop the footer from the op stream.
current_op_num--;
done = true;
break;
}
}
// Position for next cluster read
off_t offs = lseek(fd_.get(), pos, SEEK_SET);
if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
PLOG(ERROR) << "lseek next op failed";
return false;
}
pos = static_cast<uint64_t>(offs);
if (current_op.type == kCowLabelOp) {
last_label_ = {current_op.source};
// If we reach the requested label, stop reading.
if (label && label.value() == current_op.source) {
break;
}
} else if (current_op.type == kCowFooterOp) {
footer_.emplace();
CowFooter* footer = &footer_.value();
memcpy(&footer_->op, &current_op, sizeof(footer->op));
if (!android::base::ReadFully(fd_, &footer->data, sizeof(footer->data))) {
LOG(ERROR) << "Could not read COW footer";
return false;
}
// Drop the footer from the op stream.
ops_buffer->pop_back();
break;
}
ops_buffer->resize(current_op_num);
}
// To successfully parse a COW file, we need either:
@ -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

View file

@ -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;
if (!GetDataPos(&pos)) {
PLOG(ERROR) << "failed to get file position";
// Footer should be at the end of a file, so if there is data after the current block, end it
// and start a new cluster.
if (cluster_size_ && current_data_size_ > 0) {
EmitCluster();
extra_cluster = true;
}
footer_.op.ops_size = ops_.size();
if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
PLOG(ERROR) << "Failed to seek to footer position.";
return false;
}
memset(&footer_.data.ops_checksum, 0, sizeof(uint8_t) * 32);
@ -364,16 +402,24 @@ 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() {
return next_op_pos_ + sizeof(footer_);
if (current_data_size_ > 0) {
return next_data_pos_ + sizeof(footer_);
} else {
return next_op_pos_ + sizeof(footer_);
}
}
bool CowWriter::GetDataPos(uint64_t* pos) {
@ -387,6 +433,15 @@ bool CowWriter::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;
}

View file

@ -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

View file

@ -33,6 +33,9 @@ struct CowOptions {
// Maximum number of blocks that can be written.
std::optional<uint64_t> max_blocks;
// Number of CowOperations in a cluster. 0 for no clustering. Cannot be 1.
uint32_t cluster_ops = 0;
};
// Interface for writing to a snapuserd COW. All operations are ordered; merges
@ -111,6 +114,7 @@ class CowWriter : public ICowWriter {
virtual bool EmitLabel(uint64_t label) override;
private:
bool EmitCluster();
void SetupHeaders();
bool ParseOptions();
bool OpenForWrite();
@ -120,6 +124,7 @@ class CowWriter : public ICowWriter {
bool WriteOperation(const CowOperation& op, const void* data = nullptr, size_t size = 0);
void AddOperation(const CowOperation& op);
std::basic_string<uint8_t> Compress(const void* data, size_t length);
void InitPos();
bool SetFd(android::base::borrowed_fd fd);
bool Sync();
@ -132,6 +137,10 @@ class CowWriter : public ICowWriter {
CowFooter footer_{};
int compression_ = 0;
uint64_t next_op_pos_ = 0;
uint64_t next_data_pos_ = 0;
uint32_t cluster_size_ = 0;
uint32_t current_cluster_size_ = 0;
uint64_t current_data_size_ = 0;
bool is_dev_null_ = false;
bool merge_in_progress_ = false;
bool is_block_device_ = false;

View file

@ -14,6 +14,7 @@
// limitations under the License.
//
#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <string>
@ -34,7 +35,11 @@ void MyLogger(android::base::LogId, android::base::LogSeverity severity, const c
}
}
static bool Inspect(const std::string& path) {
static void usage(void) {
LOG(ERROR) << "Usage: inspect_cow [-s] <COW_FILE>";
}
static bool Inspect(const std::string& path, bool silent) {
android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
if (fd < 0) {
PLOG(ERROR) << "open failed: " << path;
@ -52,19 +57,29 @@ static bool Inspect(const std::string& path) {
LOG(ERROR) << "could not get header: " << path;
return false;
}
CowFooter footer;
bool has_footer = false;
if (reader.GetFooter(&footer)) has_footer = true;
std::cout << "Major version: " << header.major_version << "\n";
std::cout << "Minor version: " << header.minor_version << "\n";
std::cout << "Header size: " << header.header_size << "\n";
std::cout << "Footer size: " << header.footer_size << "\n";
std::cout << "Block size: " << header.block_size << "\n";
std::cout << "\n";
if (!silent) {
std::cout << "Major version: " << header.major_version << "\n";
std::cout << "Minor version: " << header.minor_version << "\n";
std::cout << "Header size: " << header.header_size << "\n";
std::cout << "Footer size: " << header.footer_size << "\n";
std::cout << "Block size: " << header.block_size << "\n";
std::cout << "\n";
if (has_footer) {
std::cout << "Total Ops size: " << footer.op.ops_size << "\n";
std::cout << "Number of Ops: " << footer.op.num_ops << "\n";
std::cout << "\n";
}
}
auto iter = reader.GetOpIter();
while (!iter->Done()) {
const CowOperation& op = iter->Get();
std::cout << op << "\n";
if (!silent) std::cout << op << "\n";
iter->Next();
}
@ -76,14 +91,25 @@ static bool Inspect(const std::string& path) {
} // namespace android
int main(int argc, char** argv) {
int ch;
bool silent = false;
while ((ch = getopt(argc, argv, "s")) != -1) {
switch (ch) {
case 's':
silent = true;
break;
default:
android::snapshot::usage();
}
}
android::base::InitLogging(argv, android::snapshot::MyLogger);
if (argc < 2) {
LOG(ERROR) << "Usage: inspect_cow <COW_FILE>";
if (argc < optind + 1) {
android::snapshot::usage();
return 1;
}
if (!android::snapshot::Inspect(argv[1])) {
if (!android::snapshot::Inspect(argv[optind], silent)) {
return 1;
}
return 0;

View file

@ -53,6 +53,7 @@ static constexpr uint64_t kBlockSize = 4096;
DEFINE_string(source_tf, "", "Source target files (dir or zip file) for incremental payloads");
DEFINE_string(compression, "gz", "Compression type to use (none or gz)");
DEFINE_uint32(cluster_ops, 0, "Number of Cow Ops per cluster (0 or >1)");
void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
unsigned int, const char* message) {
@ -189,6 +190,7 @@ bool PayloadConverter::ProcessPartition(const PartitionUpdate& update) {
CowOptions options;
options.block_size = kBlockSize;
options.compression = FLAGS_compression;
options.cluster_ops = FLAGS_cluster_ops;
writer_ = std::make_unique<CowWriter>(options);
if (!writer_->Initialize(std::move(fd))) {

View file

@ -90,7 +90,7 @@ bool CompressedSnapshotReader::SetCow(std::unique_ptr<CowReader>&& cow) {
op_iter_ = cow_->GetOpIter();
while (!op_iter_->Done()) {
const CowOperation* op = &op_iter_->Get();
if (op->type == kCowLabelOp || op->type == kCowFooterOp) {
if (IsMetadataOp(*op)) {
op_iter_->Next();
continue;
}

View file

@ -545,7 +545,7 @@ bool Snapuserd::ReadMetadata() {
struct disk_exception* de =
reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
if (cow_op->type == kCowFooterOp || cow_op->type == kCowLabelOp) {
if (IsMetadataOp(*cow_op)) {
cowop_riter_->Next();
continue;
}