diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp index 4db6584ed..35020f43c 100644 --- a/fs_mgr/libsnapshot/cow_api_test.cpp +++ b/fs_mgr/libsnapshot/cow_api_test.cpp @@ -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(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(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(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 diff --git a/fs_mgr/libsnapshot/cow_format.cpp b/fs_mgr/libsnapshot/cow_format.cpp index 49ba11f7c..0753c492e 100644 --- a/fs_mgr/libsnapshot/cow_format.cpp +++ b/fs_mgr/libsnapshot/cow_format.cpp @@ -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 diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp index 09070325a..3fbc5f32a 100644 --- a/fs_mgr/libsnapshot/cow_reader.cpp +++ b/fs_mgr/libsnapshot/cow_reader.cpp @@ -81,6 +81,24 @@ bool CowReader::Parse(android::base::borrowed_fd fd, std::optional 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 label) { } auto ops_buffer = std::make_shared>(); + 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(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(offs)) { PLOG(ERROR) << "lseek next op failed"; return false; } - pos = static_cast(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 diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp index f7cc6ff6b..85352527a 100644 --- a/fs_mgr/libsnapshot/cow_writer.cpp +++ b/fs_mgr/libsnapshot/cow_writer.cpp @@ -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::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(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 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(&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(&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; } diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h index 80766ff2f..797b8ef4c 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h @@ -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 diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h index 36cd238e0..fd43cce99 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h @@ -33,6 +33,9 @@ struct CowOptions { // Maximum number of blocks that can be written. std::optional 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 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; diff --git a/fs_mgr/libsnapshot/inspect_cow.cpp b/fs_mgr/libsnapshot/inspect_cow.cpp index 6046bad66..5ad61f37d 100644 --- a/fs_mgr/libsnapshot/inspect_cow.cpp +++ b/fs_mgr/libsnapshot/inspect_cow.cpp @@ -14,6 +14,7 @@ // limitations under the License. // #include +#include #include #include @@ -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] "; +} + +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 "; + 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; diff --git a/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp b/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp index d0b8f52e0..6a5754de1 100644 --- a/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp +++ b/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp @@ -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(options); if (!writer_->Initialize(std::move(fd))) { diff --git a/fs_mgr/libsnapshot/snapshot_reader.cpp b/fs_mgr/libsnapshot/snapshot_reader.cpp index b56d879a8..5ee8e25d1 100644 --- a/fs_mgr/libsnapshot/snapshot_reader.cpp +++ b/fs_mgr/libsnapshot/snapshot_reader.cpp @@ -90,7 +90,7 @@ bool CompressedSnapshotReader::SetCow(std::unique_ptr&& 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; } diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp index d1d252166..34254a352 100644 --- a/fs_mgr/libsnapshot/snapuserd.cpp +++ b/fs_mgr/libsnapshot/snapuserd.cpp @@ -545,7 +545,7 @@ bool Snapuserd::ReadMetadata() { struct disk_exception* de = reinterpret_cast((char*)de_ptr.get() + offset); - if (cow_op->type == kCowFooterOp || cow_op->type == kCowLabelOp) { + if (IsMetadataOp(*cow_op)) { cowop_riter_->Next(); continue; }