// If we use this function to write the offset and length (type size_t), their values should not // exceed 2^63; because the signed bit will be casted away. staticinlinebool Write8(int fd, int64_t value) { return android::base::WriteFully(fd, &value, sizeof(int64_t));
}
// Similarly, the value should not exceed 2^31 if we are casting from size_t (e.g. target chunk // size). staticinlinebool Write4(int fd, int32_t value) { return android::base::WriteFully(fd, &value, sizeof(int32_t));
}
// Trim the head or tail to align with the block size. Return false if the chunk has nothing left // after alignment. staticbool AlignHead(size_t* start, size_t* length) {
size_t residual = (*start % BLOCK_SIZE == 0) ? 0 : BLOCK_SIZE - *start % BLOCK_SIZE;
if (*length <= residual) {
*length = 0; returnfalse;
}
// Trim the data in the beginning.
*start += residual;
*length -= residual; returntrue;
}
// Trim the data in the end.
*length -= residual; returntrue;
}
// Remove the used blocks from the source chunk to make sure the source ranges are mutually // exclusive after split. Return false if we fail to get the non-overlapped ranges. In such // a case, we'll skip the entire source chunk. staticbool RemoveUsedBlocks(size_t* start, size_t* length, const SortedRangeSet& used_ranges) { if (!used_ranges.Overlaps(*start, *length)) { returntrue;
}
// TODO find the largest non-overlap chunk.
LOG(INFO) << "Removing block " << used_ranges.ToString() << " from " << *start << " - "
<< *start + *length - 1;
// If there's no duplicate entry name, we should only overlap in the head or tail block. Try to // trim both blocks. Skip this source chunk in case it still overlaps with the used ranges. if (AlignHead(start, length) && !used_ranges.Overlaps(*start, *length)) { returntrue;
} if (AlignTail(start, length) && !used_ranges.Overlaps(*start, *length)) { returntrue;
}
LOG(WARNING) << "Failed to remove the overlapped block ranges; skip the source"; returnfalse;
}
void ImageChunk::ChangeDeflateChunkToNormal() { if (type_ != CHUNK_DEFLATE) return;
type_ = CHUNK_NORMAL; // No need to clear the entry name.
uncompressed_data_.clear();
}
int fd = mkstemp(ptemp); if (fd == -1) {
PLOG(ERROR) << "MakePatch failed to create a temporary file"; returnfalse;
}
close(fd);
int r = bsdiff::bsdiff(src.DataForPatch(), src.DataLengthForPatch(), tgt.DataForPatch(),
tgt.DataLengthForPatch(), ptemp, bsdiff_cache); if (r != 0) {
LOG(ERROR) << "bsdiff() failed: " << r; returnfalse;
}
android::base::unique_fd patch_fd(open(ptemp, O_RDONLY)); if (patch_fd == -1) {
PLOG(ERROR) << "Failed to open " << ptemp; returnfalse;
} struct stat st; if (fstat(patch_fd, &st) != 0) {
PLOG(ERROR) << "Failed to stat patch file " << ptemp; returnfalse;
}
size_t sz = static_cast<size_t>(st.st_size);
patch_data->resize(sz); if (!android::base::ReadFully(patch_fd, patch_data->data(), sz)) {
PLOG(ERROR) << "Failed to read " << ptemp;
unlink(ptemp); returnfalse;
}
unlink(ptemp);
returntrue;
}
bool ImageChunk::ReconstructDeflateChunk() { if (type_ != CHUNK_DEFLATE) {
LOG(ERROR) << "Attempted to reconstruct non-deflate chunk"; returnfalse;
}
// We only check two combinations of encoder parameters: level 6 (the default) and level 9 // (the maximum). for (int level = 6; level <= 9; level += 3) { if (TryReconstruction(level)) {
compress_level_ = level; returntrue;
}
}
// Write the contents of |patch_chunks| to |patch_fd|. bool PatchChunk::WritePatchDataToFd(const std::vector<PatchChunk>& patch_chunks, int patch_fd) { // Figure out how big the imgdiff file header is going to be, so that we can correctly compute // the offset of each bsdiff patch within the file.
size_t total_header_size = 12; for (constauto& patch : patch_chunks) {
total_header_size += patch.GetHeaderSize();
}
size_t offset = total_header_size;
// Write out the headers. if (!android::base::WriteStringToFd("IMGDIFF" + std::to_string(VERSION), patch_fd)) {
PLOG(ERROR) << "Failed to write \"IMGDIFF" << VERSION << "\""; returnfalse;
}
Write4(patch_fd, static_cast<int32_t>(patch_chunks.size()));
LOG(INFO) << "Writing " << patch_chunks.size() << " patch headers..."; for (size_t i = 0; i < patch_chunks.size(); ++i) {
offset = patch_chunks[i].WriteHeaderToFd(patch_fd, offset, i);
}
// Append each chunk's bsdiff patch, in order. for (constauto& patch : patch_chunks) { if (patch.type_ == CHUNK_RAW) { continue;
} if (!android::base::WriteFully(patch_fd, patch.data_.data(), patch.data_.size())) {
PLOG(ERROR) << "Failed to write " << patch.data_.size() << " bytes patch to patch_fd"; returnfalse;
}
}
void Image::MergeAdjacentNormalChunks() {
size_t merged_last = 0, cur = 0; while (cur < chunks_.size()) { // Look for normal chunks adjacent to the current one. If such chunk exists, extend the // length of the current normal chunk.
size_t to_check = cur + 1; while (to_check < chunks_.size() && chunks_[cur].IsAdjacentNormal(chunks_[to_check])) {
chunks_[cur].MergeAdjacentNormal(chunks_[to_check]);
to_check++;
}
if (merged_last != cur) {
chunks_[merged_last] = std::move(chunks_[cur]);
}
merged_last++;
cur = to_check;
} if (merged_last < chunks_.size()) {
chunks_.erase(chunks_.begin() + merged_last, chunks_.end());
}
}
void Image::DumpChunks() const {
std::string type = is_source_ ? "source" : "target";
LOG(INFO) << "Dumping chunks for " << type; for (size_t i = 0; i < chunks_.size(); ++i) {
chunks_[i].Dump(i);
}
}
// Omit the trailing zeros before we pass the file to ziparchive handler.
size_t zipfile_size; if (!GetZipFileSize(&zipfile_size)) {
LOG(ERROR) << "Failed to parse the actual size of " << filename; returnfalse;
}
ZipArchiveHandle handle; int err = OpenArchiveFromMemory(const_cast<uint8_t*>(file_content_.data()), zipfile_size,
filename.c_str(), &handle); if (err != 0) {
LOG(ERROR) << "Failed to open zip file " << filename << ": " << ErrorCodeString(err);
CloseArchive(handle); returnfalse;
}
if (!InitializeChunks(filename, handle)) {
CloseArchive(handle); returnfalse;
}
CloseArchive(handle); returntrue;
}
// Iterate the zip entries and compose the image chunks accordingly. bool ZipModeImage::InitializeChunks(const std::string& filename, ZipArchiveHandle handle) { void* cookie; int ret = StartIteration(handle, &cookie); if (ret != 0) {
LOG(ERROR) << "Failed to iterate over entries in " << filename << ": " << ErrorCodeString(ret); returnfalse;
}
// Create a list of deflated zip entries, sorted by offset.
std::vector<std::pair<std::string, ZipEntry64>> temp_entries;
std::string name;
ZipEntry64 entry; while ((ret = Next(cookie, &entry, &name)) == 0) { if (entry.method == kCompressDeflated || limit_ > 0) {
temp_entries.emplace_back(name, entry);
}
}
if (ret != -1) {
LOG(ERROR) << "Error while iterating over zip entries: " << ErrorCodeString(ret); returnfalse;
}
std::sort(temp_entries.begin(), temp_entries.end(),
[](auto& entry1, auto& entry2) { return entry1.second.offset < entry2.second.offset; });
EndIteration(cookie);
// For source chunks, we don't need to compose chunks for the metadata. if (is_source_) { for (auto& entry : temp_entries) { if (!AddZipEntryToChunks(handle, entry.first, &entry.second)) {
LOG(ERROR) << "Failed to add " << entry.first << " to source chunks"; returnfalse;
}
}
// Add the end of zip file (mainly central directory) as a normal chunk.
size_t entries_end = 0; if (!temp_entries.empty()) {
CHECK_GE(temp_entries.back().second.offset, 0); if (__builtin_add_overflow(temp_entries.back().second.offset,
temp_entries.back().second.compressed_length, &entries_end)) {
LOG(ERROR) << "`entries_end` overflows on entry with offset "
<< temp_entries.back().second.offset << " and compressed_length "
<< temp_entries.back().second.compressed_length; returnfalse;
}
}
CHECK_LT(entries_end, file_content_.size());
chunks_.emplace_back(CHUNK_NORMAL, entries_end, &file_content_,
file_content_.size() - entries_end);
returntrue;
}
// For target chunks, add the deflate entries as CHUNK_DEFLATE and the contents between two // deflate entries as CHUNK_NORMAL.
size_t pos = 0;
size_t nextentry = 0; while (pos < file_content_.size()) { if (nextentry < temp_entries.size() && static_cast<off64_t>(pos) == temp_entries[nextentry].second.offset) { // Add the next zip entry.
std::string entry_name = temp_entries[nextentry].first; if (!AddZipEntryToChunks(handle, entry_name, &temp_entries[nextentry].second)) {
LOG(ERROR) << "Failed to add " << entry_name << " to target chunks"; returnfalse;
} if (temp_entries[nextentry].second.compressed_length > std::numeric_limits<size_t>::max()) {
LOG(ERROR) << "Entry " << name << " compressed size exceeds size of address space. "
<< entry.compressed_length; returnfalse;
} if (__builtin_add_overflow(pos, temp_entries[nextentry].second.compressed_length, &pos)) {
LOG(ERROR) << "`pos` overflows after adding "
<< temp_entries[nextentry].second.compressed_length; returnfalse;
}
++nextentry; continue;
}
// Use a normal chunk to take all the data up to the start of the next entry.
size_t raw_data_len; if (nextentry < temp_entries.size()) {
raw_data_len = temp_entries[nextentry].second.offset - pos;
} else {
raw_data_len = file_content_.size() - pos;
}
chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, raw_data_len);
// EOCD record // offset 0: signature 0x06054b50, 4 bytes // offset 4: number of this disk, 2 bytes // ... // offset 20: comment length, 2 bytes // offset 22: comment, n bytes bool ZipModeImage::GetZipFileSize(size_t* input_file_size) { if (file_content_.size() < 22) {
LOG(ERROR) << "File is too small to be a zip file"; returnfalse;
}
// Look for End of central directory record of the zip file, and calculate the actual // zip_file size. for (int i = file_content_.size() - 22; i >= 0; i--) { if (file_content_[i] == 0x50) { if (get_unaligned<uint32_t>(&file_content_[i]) == 0x06054b50) { // double-check: this archive consists of a single "disk".
CHECK_EQ(get_unaligned<uint16_t>(&file_content_[i + 4]), 0);
const ImageChunk* ZipModeImage::FindChunkByName(const std::string& name, bool find_normal) const { if (name.empty()) { return nullptr;
} for (auto& chunk : chunks_) { if (chunk.GetType() != CHUNK_DEFLATE && !find_normal) { continue;
}
if (chunk.GetEntryName() == name) { return &chunk;
}
// Edge case when target chunk is split due to size limit but source chunk isn't. if (name == (chunk.GetEntryName() + "-0") || chunk.GetEntryName() == (name + "-0")) { return &chunk;
}
// TODO handle the .so files with incremental version number. // (e.g. lib/arm64-v8a/libcronet.59.0.3050.4.so)
}
bool ZipModeImage::CheckAndProcessChunks(ZipModeImage* tgt_image, ZipModeImage* src_image) { for (auto& tgt_chunk : *tgt_image) { if (tgt_chunk.GetType() != CHUNK_DEFLATE) { continue;
}
ImageChunk* src_chunk = src_image->FindChunkByName(tgt_chunk.GetEntryName()); if (src_chunk == nullptr) {
tgt_chunk.ChangeDeflateChunkToNormal();
} elseif (tgt_chunk == *src_chunk) { // If two deflate chunks are identical (eg, the kernel has not changed between two builds), // treat them as normal chunks. This makes applypatch much faster -- it can apply a trivial // patch to the compressed data, rather than uncompressing and recompressing to apply the // trivial patch to the uncompressed data.
tgt_chunk.ChangeDeflateChunkToNormal();
src_chunk->ChangeDeflateChunkToNormal();
} elseif (!tgt_chunk.ReconstructDeflateChunk()) { // We cannot recompress the data and get exactly the same bits as are in the input target // image. Treat the chunk as a normal non-deflated chunk.
LOG(WARNING) << "Failed to reconstruct target deflate chunk [" << tgt_chunk.GetEntryName()
<< "]; treating as normal";
// For zips, we only need merge normal chunks for the target: deflated chunks are matched via // filename, and normal chunks are patched using the entire source file as the source. if (tgt_image->limit_ == 0) {
tgt_image->MergeAdjacentNormalChunks();
tgt_image->DumpChunks();
}
returntrue;
}
// For each target chunk, look for the corresponding source chunk by the zip_entry name. If // found, add the range of this chunk in the original source file to the block aligned source // ranges. Construct the split src & tgt image once the size of source range reaches limit. bool ZipModeImage::SplitZipModeImageWithLimit(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
std::vector<ZipModeImage>* split_tgt_images,
std::vector<ZipModeImage>* split_src_images,
std::vector<SortedRangeSet>* split_src_ranges) {
CHECK_EQ(tgt_image.limit_, src_image.limit_);
size_t limit = tgt_image.limit_;
SortedRangeSet used_src_ranges; // ranges used for previous split source images.
// Reserve the central directory in advance for the last split image. constauto& central_directory = src_image.cend() - 1;
CHECK_EQ(CHUNK_NORMAL, central_directory->GetType());
used_src_ranges.Insert(central_directory->GetStartOffset(),
central_directory->DataLengthForPatch());
// Make sure this source range hasn't been used before so that the src_range pieces don't // overlap with each other. if (!RemoveUsedBlocks(&src_offset, &src_length, used_src_ranges)) {
split_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt->GetStartOffset(), &tgt_image.file_content_,
tgt->GetRawDataLength());
} elseif (src_ranges.blocks() * BLOCK_SIZE + src_length <= limit) {
src_ranges.Insert(src_offset, src_length);
// Add the deflate source chunk if it hasn't been aligned. if (src->GetType() == CHUNK_DEFLATE && src_length == src->GetRawDataLength()) {
split_src_chunks.push_back(*src);
split_tgt_chunks.push_back(*tgt);
} else { // TODO split smarter to avoid alignment of large deflate chunks
split_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt->GetStartOffset(), &tgt_image.file_content_,
tgt->GetRawDataLength());
}
} else { bool added_image = ZipModeImage::AddSplitImageFromChunkList(
tgt_image, src_image, src_ranges, split_tgt_chunks, split_src_chunks, split_tgt_images,
split_src_images);
split_tgt_chunks.clear();
split_src_chunks.clear(); // No need to update the split_src_ranges if we don't update the split source images. if (added_image) {
used_src_ranges.Insert(src_ranges);
split_src_ranges->push_back(std::move(src_ranges));
}
src_ranges = {};
// We don't have enough space for the current chunk; start a new split image and handle // this chunk there.
tgt--;
}
}
// TODO Trim it in case the CD exceeds limit too much.
src_ranges.Insert(central_directory->GetStartOffset(), central_directory->DataLengthForPatch()); bool added_image = ZipModeImage::AddSplitImageFromChunkList(tgt_image, src_image, src_ranges,
split_tgt_chunks, split_src_chunks,
split_tgt_images, split_src_images); if (added_image) {
split_src_ranges->push_back(std::move(src_ranges));
}
// Align the target chunks in the beginning with BLOCK_SIZE.
size_t i = 0; while (i < split_tgt_chunks.size()) {
size_t tgt_start = split_tgt_chunks[i].GetStartOffset();
size_t tgt_length = split_tgt_chunks[i].GetRawDataLength();
// Current ImageChunk is long enough to align. if (AlignHead(&tgt_start, &tgt_length)) {
aligned_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt_start, &tgt_image.file_content_,
tgt_length); break;
}
i++;
}
// Nothing left after alignment in the current split tgt chunks; skip adding the split_tgt_image. if (i == split_tgt_chunks.size()) { returnfalse;
}
aligned_tgt_chunks.insert(aligned_tgt_chunks.end(), split_tgt_chunks.begin() + i + 1,
split_tgt_chunks.end());
CHECK(!aligned_tgt_chunks.empty());
// Add a normal chunk to align the contents in the end.
size_t end_offset =
aligned_tgt_chunks.back().GetStartOffset() + aligned_tgt_chunks.back().GetRawDataLength(); if (end_offset % BLOCK_SIZE != 0 && end_offset < tgt_image.file_content_.size()) {
size_t tail_block_length = std::min<size_t>(tgt_image.file_content_.size() - end_offset,
BLOCK_SIZE - (end_offset % BLOCK_SIZE));
aligned_tgt_chunks.emplace_back(CHUNK_NORMAL, end_offset, &tgt_image.file_content_,
tail_block_length);
}
// Construct the split source file based on the split src ranges.
std::vector<uint8_t> split_src_content; for (constauto& r : split_src_ranges) {
size_t end = std::min(src_image.file_content_.size(), r.second * BLOCK_SIZE);
split_src_content.insert(split_src_content.end(),
src_image.file_content_.begin() + r.first * BLOCK_SIZE,
src_image.file_content_.begin() + end);
}
// We should not have an empty src in our design; otherwise we will encounter an error in // bsdiff since split_src_content.data() == nullptr.
CHECK(!split_src_content.empty());
// Verify that the target image pieces is continuous and can add up to the total size.
size_t last_offset = 0; for (constauto& tgt_image : split_tgt_images) {
CHECK(!tgt_image.chunks_.empty());
// Write the split source & patch into the debug directory. if (!debug_dir.empty()) {
std::string src_name = android::base::StringPrintf("%s/src-%zu", debug_dir.c_str(), i);
android::base::unique_fd fd(
open(src_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
if (fd == -1) {
PLOG(ERROR) << "Failed to open " << src_name; returnfalse;
} if (!android::base::WriteFully(fd, split_src_images[i].PseudoSource().DataForPatch(),
split_src_images[i].PseudoSource().DataLengthForPatch())) {
PLOG(ERROR) << "Failed to write split source data into " << src_name; returnfalse;
}
if (fd == -1) {
PLOG(ERROR) << "Failed to open " << patch_name; returnfalse;
} if (!PatchChunk::WritePatchDataToFd(patch_chunks, fd)) { returnfalse;
}
}
}
// Store the split in the following format: // Line 0: imgdiff version# // Line 1: number of pieces // Line 2: patch_size_1 tgt_size_1 src_range_1 // ... // Line n+1: patch_size_n tgt_size_n src_range_n
std::string split_info_string = android::base::StringPrintf( "%zu\n%zu\n", VERSION, split_info_list.size()) + android::base::Join(split_info_list, '\n'); if (!android::base::WriteStringToFile(split_info_string, split_info_file)) {
PLOG(ERROR) << "Failed to write split info to " << split_info_file; returnfalse;
}
size_t sz = file_content_.size();
size_t pos = 0; while (pos < sz) { // 0x00 no header flags, 0x08 deflate compression, 0x1f8b gzip magic number if (sz - pos >= 4 && get_unaligned<uint32_t>(file_content_.data() + pos) == 0x00088b1f) { // 'pos' is the offset of the start of a gzip chunk.
size_t chunk_offset = pos;
// The remaining data is too small to be a gzip chunk; treat them as a normal chunk. if (sz - pos < GZIP_HEADER_LEN + GZIP_FOOTER_LEN) {
chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, sz - pos); break;
}
// We need three chunks for the deflated image in total, one normal chunk for the header, // one deflated chunk for the body, and another normal chunk for the footer.
chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, GZIP_HEADER_LEN);
pos += GZIP_HEADER_LEN;
// We must decompress this chunk in order to discover where it ends, and so we can update // the uncompressed_data of the image body and its length.
// -15 means we are decoding a 'raw' deflate stream; zlib will // not expect zlib headers. int ret = inflateInit2(&strm, -15); if (ret < 0) {
LOG(ERROR) << "Failed to initialize inflate: " << ret; returnfalse;
}
size_t allocated = BUFFER_SIZE;
std::vector<uint8_t> uncompressed_data(allocated);
size_t uncompressed_len = 0, raw_data_len = 0; do {
strm.avail_out = allocated - uncompressed_len;
strm.next_out = uncompressed_data.data() + uncompressed_len;
ret = inflate(&strm, Z_NO_FLUSH); if (ret < 0) {
LOG(WARNING) << "Inflate failed [" << strm.msg << "] at offset [" << chunk_offset
<< "]; treating as a normal chunk"; break;
}
uncompressed_len = allocated - strm.avail_out; if (strm.avail_out == 0) {
allocated *= 2;
uncompressed_data.resize(allocated);
}
} while (ret != Z_STREAM_END);
// The footer contains the size of the uncompressed data. Double-check to make sure that it // matches the size of the data we got when we actually did the decompression.
size_t footer_index = pos + raw_data_len + GZIP_FOOTER_LEN - 4; if (sz - footer_index < 4) {
LOG(WARNING) << "invalid footer position; treating as a normal chunk"; continue;
}
size_t footer_size = get_unaligned<uint32_t>(file_content_.data() + footer_index); if (footer_size != uncompressed_len) {
LOG(WARNING) << "footer size " << footer_size << " != " << uncompressed_len
<< "; treating as a normal chunk"; continue;
}
// create a normal chunk for the footer
chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, GZIP_FOOTER_LEN);
pos += GZIP_FOOTER_LEN;
} else { // Use a normal chunk to take all the contents until the next gzip chunk (or EOF); we expect // the number of chunks to be small (5 for typical boot and recovery images).
// Scan forward until we find a gzip header.
size_t data_len = 0; while (data_len + pos < sz) { if (data_len + pos + 4 <= sz &&
get_unaligned<uint32_t>(file_content_.data() + pos + data_len) == 0x00088b1f) { break;
}
data_len++;
}
chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, data_len);
pos += data_len;
}
}
returntrue;
}
bool ImageModeImage::SetBonusData(const std::vector<uint8_t>& bonus_data) {
CHECK(is_source_); if (chunks_.size() < 2 || !chunks_[1].SetBonusData(bonus_data)) {
LOG(ERROR) << "Failed to set bonus data";
DumpChunks(); returnfalse;
}
LOG(INFO) << " using " << bonus_data.size() << " bytes of bonus data"; returntrue;
}
// In Image Mode, verify that the source and target images have the same chunk structure (ie, the // same sequence of deflate and normal chunks). bool ImageModeImage::CheckAndProcessChunks(ImageModeImage* tgt_image, ImageModeImage* src_image) { // In image mode, merge the gzip header and footer in with any adjacent normal chunks.
tgt_image->MergeAdjacentNormalChunks();
src_image->MergeAdjacentNormalChunks();
if (tgt_image->NumOfChunks() != src_image->NumOfChunks()) {
LOG(ERROR) << "Source and target don't have same number of chunks!";
tgt_image->DumpChunks();
src_image->DumpChunks(); returnfalse;
} for (size_t i = 0; i < tgt_image->NumOfChunks(); ++i) { if ((*tgt_image)[i].GetType() != (*src_image)[i].GetType()) {
LOG(ERROR) << "Source and target don't have same chunk structure! (chunk " << i << ")";
tgt_image->DumpChunks();
src_image->DumpChunks(); returnfalse;
}
}
for (size_t i = 0; i < tgt_image->NumOfChunks(); ++i) { auto& tgt_chunk = (*tgt_image)[i]; auto& src_chunk = (*src_image)[i]; if (tgt_chunk.GetType() != CHUNK_DEFLATE) { continue;
}
// If two deflate chunks are identical treat them as normal chunks. if (tgt_chunk == src_chunk) {
tgt_chunk.ChangeDeflateChunkToNormal();
src_chunk.ChangeDeflateChunkToNormal();
} elseif (!tgt_chunk.ReconstructDeflateChunk()) { // We cannot recompress the data and get exactly the same bits as are in the input target // image, fall back to normal
LOG(WARNING) << "Failed to reconstruct target deflate chunk " << i << " ["
<< tgt_chunk.GetEntryName() << "]; treating as normal";
tgt_chunk.ChangeDeflateChunkToNormal();
src_chunk.ChangeDeflateChunkToNormal();
}
}
// For images, we need to maintain the parallel structure of the chunk lists, so do the merging // in both the source and target lists.
tgt_image->MergeAdjacentNormalChunks();
src_image->MergeAdjacentNormalChunks(); if (tgt_image->NumOfChunks() != src_image->NumOfChunks()) { // This shouldn't happen.
LOG(ERROR) << "Merging normal chunks went awry"; returnfalse;
}
returntrue;
}
// In image mode, generate patches against the given source chunks and bonus_data; write the // result to |patch_name|. bool ImageModeImage::GeneratePatches(const ImageModeImage& tgt_image, const ImageModeImage& src_image, const std::string& patch_name) {
LOG(INFO) << "Constructing patches for " << tgt_image.NumOfChunks() << " chunks...";
std::vector<PatchChunk> patch_chunks;
patch_chunks.reserve(tgt_image.NumOfChunks());
for (size_t i = 0; i < tgt_image.NumOfChunks(); i++) { constauto& tgt_chunk = tgt_image[i]; constauto& src_chunk = src_image[i];
if (PatchChunk::RawDataIsSmaller(tgt_chunk, 0)) {
patch_chunks.emplace_back(tgt_chunk); continue;
}
std::vector<uint8_t> patch_data; if (!ImageChunk::MakePatch(tgt_chunk, src_chunk, &patch_data, nullptr)) {
LOG(ERROR) << "Failed to generate patch for target chunk " << i; returnfalse;
}
LOG(INFO) << "patch " << i << " is " << patch_data.size() << " bytes (of "
<< tgt_chunk.GetRawDataLength() << ")";
if (!verbose) {
android::base::SetMinimumLogSeverity(android::base::WARNING);
}
if (argc - optind != 3) {
LOG(ERROR) << "usage: " << argv[0] << " [options] <src-img> <tgt-img> <patch-file>";
LOG(ERROR)
<< " -z <zip-mode>, Generate patches in zip mode, src and tgt should be zip files.\n" " -b <bonus-file>, Bonus file in addition to src, image mode only.\n" " --block-limit, For large zips, split the src and tgt based on the block limit;\n" " and generate patches between each pair of pieces. Concatenate " "these\n" " patches together and output them into <patch-file>.\n" " --split-info, Output the split information (patch_size, tgt_size, src_ranges);\n" " zip mode with block-limit only.\n" " --debug-dir, Debug directory to put the split srcs and patches, zip mode only.\n" " -v, --verbose, Enable verbose logging."; return2;
}
if (!src_image.Initialize(argv[optind])) { return1;
} if (!tgt_image.Initialize(argv[optind + 1])) { return1;
}
if (!ZipModeImage::CheckAndProcessChunks(&tgt_image, &src_image)) { return1;
}
// Compute bsdiff patches for each chunk's data (the uncompressed data, in the case of // deflate chunks). if (blocks_limit > 0) { if (split_info_file.empty()) {
LOG(ERROR) << "split-info path cannot be empty when generating patches with a block-limit"; return1;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.