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:
commit
2e67e77752
10 changed files with 409 additions and 68 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, ¤t_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, ¤t_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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue