better patching for zip files

Adds a zip mode ("-z") to imgdiff to construct efficient patches for
zip files (including jars and apks).  We identify the regions within
the zip file containing deflated data, and when a corresponding file
can be found in the source zip, a patch is generated for the
uncompressed version of the data.

The GZIP chunk type is replaced with a DEFLATE chunk type that handles
a raw deflated data stream.  This new DEFLATE chunk can be used for
both gzipped pieces (as found within boot and recovery images) and zip
files (apks, etc.)  The gzip header and footer are handled by NORMAL
chunks on either side of the main DEFLATE chunks.  (Typically these
tiny NORMAL chunks will get merged with adjacent chunks, so the number
of output chunks is unaffected.)

Add a test script that tests the generate-apply cycle on all the zips
and images within a pair of full OTA packages.
This commit is contained in:
Doug Zongker
2009-07-20 14:45:29 -07:00
parent 3b72436dbe
commit 6b2bb3d96d
8 changed files with 906 additions and 203 deletions

View File

@@ -17,7 +17,7 @@ ifneq ($(TARGET_SIMULATOR),true)
LOCAL_PATH := $(call my-dir) LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_SRC_FILES := applypatch.c bsdiff.c freecache.c imgpatch.c LOCAL_SRC_FILES := applypatch.c bsdiff.c freecache.c imgpatch.c utils.c
LOCAL_MODULE := libapplypatch LOCAL_MODULE := libapplypatch
LOCAL_MODULE_TAGS := eng LOCAL_MODULE_TAGS := eng
LOCAL_C_INCLUDES += external/bzip2 external/zlib bootable/recovery LOCAL_C_INCLUDES += external/bzip2 external/zlib bootable/recovery
@@ -39,7 +39,7 @@ include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_SRC_FILES := imgdiff.c LOCAL_SRC_FILES := imgdiff.c utils.c
LOCAL_MODULE := imgdiff LOCAL_MODULE := imgdiff
LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_FORCE_STATIC_EXECUTABLE := true
LOCAL_MODULE_TAGS := eng LOCAL_MODULE_TAGS := eng

View File

@@ -765,7 +765,8 @@ int applypatch(int argc, char** argv) {
return result; return result;
} }
} else if (header_bytes_read >= 8 && } else if (header_bytes_read >= 8 &&
memcmp(header, "IMGDIFF1", 8) == 0) { memcmp(header, "IMGDIFF", 7) == 0 &&
(header[7] == '1' || header[7] == '2')) {
int result = ApplyImagePatch(source_to_use->data, source_to_use->size, int result = ApplyImagePatch(source_to_use->data, source_to_use->size,
patch_filename, output, &ctx); patch_filename, output, &ctx);
if (result != 0) { if (result != 0) {
@@ -773,7 +774,7 @@ int applypatch(int argc, char** argv) {
return result; return result;
} }
} else { } else {
fprintf(stderr, "Unknown patch file format"); fprintf(stderr, "Unknown patch file format\n");
return 1; return 1;
} }

View File

@@ -64,11 +64,15 @@
* "IMGDIFF1" (8) [magic number and version] * "IMGDIFF1" (8) [magic number and version]
* chunk count (4) * chunk count (4)
* for each chunk: * for each chunk:
* chunk type (4) [CHUNK_NORMAL or CHUNK_GZIP] * chunk type (4) [CHUNK_{NORMAL, GZIP, DEFLATE, RAW}]
* source start (8) * if chunk type == CHUNK_NORMAL:
* source len (8) * source start (8)
* bsdiff patch offset (8) [from start of patch file] * source len (8)
* if chunk type == CHUNK_GZIP: * bsdiff patch offset (8) [from start of patch file]
* if chunk type == CHUNK_GZIP: (version 1 only)
* source start (8)
* source len (8)
* bsdiff patch offset (8) [from start of patch file]
* source expanded len (8) [size of uncompressed source] * source expanded len (8) [size of uncompressed source]
* target expected len (8) [size of uncompressed target] * target expected len (8) [size of uncompressed target]
* gzip level (4) * gzip level (4)
@@ -79,6 +83,20 @@
* gzip header len (4) * gzip header len (4)
* gzip header (gzip header len) * gzip header (gzip header len)
* gzip footer (8) * gzip footer (8)
* if chunk type == CHUNK_DEFLATE: (version 2 only)
* source start (8)
* source len (8)
* bsdiff patch offset (8) [from start of patch file]
* source expanded len (8) [size of uncompressed source]
* target expected len (8) [size of uncompressed target]
* gzip level (4)
* method (4)
* windowBits (4)
* memLevel (4)
* strategy (4)
* if chunk type == RAW: (version 2 only)
* target len (4)
* data (target len)
* *
* All integers are little-endian. "source start" and "source len" * All integers are little-endian. "source start" and "source len"
* specify the section of the input image that comprises this chunk, * specify the section of the input image that comprises this chunk,
@@ -104,29 +122,230 @@
#include "zlib.h" #include "zlib.h"
#include "imgdiff.h" #include "imgdiff.h"
#include "utils.h"
typedef struct { typedef struct {
int type; // CHUNK_NORMAL or CHUNK_GZIP int type; // CHUNK_NORMAL, CHUNK_DEFLATE
size_t start; // offset of chunk in original image file size_t start; // offset of chunk in original image file
size_t len; size_t len;
unsigned char* data; // data to be patched (ie, uncompressed, for unsigned char* data; // data to be patched (uncompressed, for deflate chunks)
// gzip chunks)
// everything else is for CHUNK_GZIP chunks only: size_t source_start;
size_t source_len;
size_t gzip_header_len; // --- for CHUNK_DEFLATE chunks only: ---
unsigned char* gzip_header;
unsigned char* gzip_footer;
// original (compressed) gzip data, including header and footer // original (compressed) deflate data
size_t gzip_len; size_t deflate_len;
unsigned char* gzip_data; unsigned char* deflate_data;
char* filename; // used for zip entries
// deflate encoder parameters // deflate encoder parameters
int level, method, windowBits, memLevel, strategy; int level, method, windowBits, memLevel, strategy;
size_t source_uncompressed_len;
} ImageChunk; } ImageChunk;
typedef struct {
int data_offset;
int deflate_len;
int uncomp_len;
char* filename;
} ZipFileEntry;
static int fileentry_compare(const void* a, const void* b) {
int ao = ((ZipFileEntry*)a)->data_offset;
int bo = ((ZipFileEntry*)b)->data_offset;
if (ao < bo) {
return -1;
} else if (ao > bo) {
return 1;
} else {
return 0;
}
}
unsigned char* ReadZip(const char* filename,
int* num_chunks, ImageChunk** chunks,
int include_pseudo_chunk) {
struct stat st;
if (stat(filename, &st) != 0) {
fprintf(stderr, "failed to stat \"%s\": %s\n", filename, strerror(errno));
return NULL;
}
unsigned char* img = malloc(st.st_size);
FILE* f = fopen(filename, "rb");
if (fread(img, 1, st.st_size, f) != st.st_size) {
fprintf(stderr, "failed to read \"%s\" %s\n", filename, strerror(errno));
fclose(f);
return NULL;
}
fclose(f);
// look for the end-of-central-directory record.
int i;
for (i = st.st_size-20; i >= 0 && i > st.st_size - 65600; --i) {
if (img[i] == 0x50 && img[i+1] == 0x4b &&
img[i+2] == 0x05 && img[i+3] == 0x06) {
break;
}
}
// double-check: this archive consists of a single "disk"
if (!(img[i+4] == 0 && img[i+5] == 0 && img[i+6] == 0 && img[i+7] == 0)) {
fprintf(stderr, "can't process multi-disk archive\n");
return NULL;
}
int cdcount = Read2(img+i+8);
int cdoffset = Read4(img+i+16);
ZipFileEntry* temp_entries = malloc(cdcount * sizeof(ZipFileEntry));
int entrycount = 0;
unsigned char* cd = img+cdoffset;
for (i = 0; i < cdcount; ++i) {
if (!(cd[0] == 0x50 && cd[1] == 0x4b && cd[2] == 0x01 && cd[3] == 0x02)) {
fprintf(stderr, "bad central directory entry %d\n", i);
return NULL;
}
int clen = Read4(cd+20); // compressed len
int ulen = Read4(cd+24); // uncompressed len
int nlen = Read2(cd+28); // filename len
int xlen = Read2(cd+30); // extra field len
int mlen = Read2(cd+32); // file comment len
int hoffset = Read4(cd+42); // local header offset
char* filename = malloc(nlen+1);
memcpy(filename, cd+46, nlen);
filename[nlen] = '\0';
int method = Read2(cd+10);
cd += 46 + nlen + xlen + mlen;
if (method != 8) { // 8 == deflate
free(filename);
continue;
}
unsigned char* lh = img + hoffset;
if (!(lh[0] == 0x50 && lh[1] == 0x4b && lh[2] == 0x03 && lh[3] == 0x04)) {
fprintf(stderr, "bad local file header entry %d\n", i);
return NULL;
}
if (Read2(lh+26) != nlen || memcmp(lh+30, filename, nlen) != 0) {
fprintf(stderr, "central dir filename doesn't match local header\n");
return NULL;
}
xlen = Read2(lh+28); // extra field len; might be different from CD entry?
temp_entries[entrycount].data_offset = hoffset+30+nlen+xlen;
temp_entries[entrycount].deflate_len = clen;
temp_entries[entrycount].uncomp_len = ulen;
temp_entries[entrycount].filename = filename;
++entrycount;
}
qsort(temp_entries, entrycount, sizeof(ZipFileEntry), fileentry_compare);
#if 0
printf("found %d deflated entries\n", entrycount);
for (i = 0; i < entrycount; ++i) {
printf("off %10d len %10d unlen %10d %p %s\n",
temp_entries[i].data_offset,
temp_entries[i].deflate_len,
temp_entries[i].uncomp_len,
temp_entries[i].filename,
temp_entries[i].filename);
}
#endif
*num_chunks = 0;
*chunks = malloc((entrycount*2+2) * sizeof(ImageChunk));
ImageChunk* curr = *chunks;
if (include_pseudo_chunk) {
curr->type = CHUNK_NORMAL;
curr->start = 0;
curr->len = st.st_size;
curr->data = img;
curr->filename = NULL;
++curr;
++*num_chunks;
}
int pos = 0;
int nextentry = 0;
while (pos < st.st_size) {
if (nextentry < entrycount && pos == temp_entries[nextentry].data_offset) {
curr->type = CHUNK_DEFLATE;
curr->start = pos;
curr->deflate_len = temp_entries[nextentry].deflate_len;
curr->deflate_data = img + pos;
curr->filename = temp_entries[nextentry].filename;
curr->len = temp_entries[nextentry].uncomp_len;
curr->data = malloc(curr->len);
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = curr->deflate_len;
strm.next_in = curr->deflate_data;
// -15 means we are decoding a 'raw' deflate stream; zlib will
// not expect zlib headers.
int ret = inflateInit2(&strm, -15);
strm.avail_out = curr->len;
strm.next_out = curr->data;
ret = inflate(&strm, Z_NO_FLUSH);
if (ret != Z_STREAM_END) {
fprintf(stderr, "failed to inflate \"%s\"; %d\n", curr->filename, ret);
return NULL;
}
inflateEnd(&strm);
pos += curr->deflate_len;
++nextentry;
++*num_chunks;
++curr;
continue;
}
// use a normal chunk to take all the data up to the start of the
// next deflate section.
curr->type = CHUNK_NORMAL;
curr->start = pos;
if (nextentry < entrycount) {
curr->len = temp_entries[nextentry].data_offset - pos;
} else {
curr->len = st.st_size - pos;
}
curr->data = img + pos;
curr->filename = NULL;
pos += curr->len;
++*num_chunks;
++curr;
}
free(temp_entries);
return img;
}
/* /*
* Read the given file and break it up into chunks, putting the number * Read the given file and break it up into chunks, putting the number
* of chunks and their info in *num_chunks and **chunks, * of chunks and their info in *num_chunks and **chunks,
@@ -166,38 +385,45 @@ unsigned char* ReadImage(const char* filename,
while (pos < st.st_size) { while (pos < st.st_size) {
unsigned char* p = img+pos; unsigned char* p = img+pos;
// Reallocate the list for every chunk; we expect the number of
// chunks to be small (5 for typical boot and recovery images).
++*num_chunks;
*chunks = realloc(*chunks, *num_chunks * sizeof(ImageChunk));
ImageChunk* curr = *chunks + (*num_chunks-1);
curr->start = pos;
if (st.st_size - pos >= 4 && if (st.st_size - pos >= 4 &&
p[0] == 0x1f && p[1] == 0x8b && p[0] == 0x1f && p[1] == 0x8b &&
p[2] == 0x08 && // deflate compression p[2] == 0x08 && // deflate compression
p[3] == 0x00) { // no header flags p[3] == 0x00) { // no header flags
// 'pos' is the offset of the start of a gzip chunk. // 'pos' is the offset of the start of a gzip chunk.
curr->type = CHUNK_GZIP; *num_chunks += 3;
curr->gzip_header_len = GZIP_HEADER_LEN; *chunks = realloc(*chunks, *num_chunks * sizeof(ImageChunk));
curr->gzip_header = p; ImageChunk* curr = *chunks + (*num_chunks-3);
// create a normal chunk for the header.
curr->start = pos;
curr->type = CHUNK_NORMAL;
curr->len = GZIP_HEADER_LEN;
curr->data = p;
pos += curr->len;
p += curr->len;
++curr;
curr->type = CHUNK_DEFLATE;
curr->filename = NULL;
// We must decompress this chunk in order to discover where it // We must decompress this chunk in order to discover where it
// ends, and so we can put the uncompressed data and its length // ends, and so we can put the uncompressed data and its length
// into curr->data and curr->len; // into curr->data and curr->len.
size_t allocated = 32768; size_t allocated = 32768;
curr->len = 0; curr->len = 0;
curr->data = malloc(allocated); curr->data = malloc(allocated);
curr->gzip_data = p; curr->start = pos;
curr->deflate_data = p;
z_stream strm; z_stream strm;
strm.zalloc = Z_NULL; strm.zalloc = Z_NULL;
strm.zfree = Z_NULL; strm.zfree = Z_NULL;
strm.opaque = Z_NULL; strm.opaque = Z_NULL;
strm.avail_in = st.st_size - (pos + curr->gzip_header_len); strm.avail_in = st.st_size - pos;
strm.next_in = p + GZIP_HEADER_LEN; strm.next_in = p;
// -15 means we are decoding a 'raw' deflate stream; zlib will // -15 means we are decoding a 'raw' deflate stream; zlib will
// not expect zlib headers. // not expect zlib headers.
@@ -214,27 +440,42 @@ unsigned char* ReadImage(const char* filename,
} }
} while (ret != Z_STREAM_END); } while (ret != Z_STREAM_END);
curr->gzip_len = st.st_size - strm.avail_in - pos + GZIP_FOOTER_LEN; curr->deflate_len = st.st_size - strm.avail_in - pos;
pos = st.st_size - strm.avail_in;
inflateEnd(&strm); inflateEnd(&strm);
pos += curr->deflate_len;
p += curr->deflate_len;
++curr;
// consume the gzip footer. // create a normal chunk for the footer
curr->gzip_footer = img+pos;
pos += GZIP_FOOTER_LEN; curr->type = CHUNK_NORMAL;
p = img+pos; curr->start = pos;
curr->len = GZIP_FOOTER_LEN;
curr->data = img+pos;
pos += curr->len;
p += curr->len;
++curr;
// The footer (that we just skipped over) contains the size of // The footer (that we just skipped over) contains the size of
// the uncompressed data. Double-check to make sure that it // the uncompressed data. Double-check to make sure that it
// matches the size of the data we got when we actually did // matches the size of the data we got when we actually did
// the decompression. // the decompression.
size_t footer_size = p[-4] + (p[-3] << 8) + (p[-2] << 16) + (p[-1] << 24); size_t footer_size = Read4(p-4);
if (footer_size != curr->len) { if (footer_size != curr[-2].len) {
fprintf(stderr, "Error: footer size %d != decompressed size %d\n", fprintf(stderr, "Error: footer size %d != decompressed size %d\n",
footer_size, curr->len); footer_size, curr[-2].len);
free(img); free(img);
return NULL; return NULL;
} }
} else { } else {
// Reallocate the list for every chunk; we expect the number of
// chunks to be small (5 for typical boot and recovery images).
++*num_chunks;
*chunks = realloc(*chunks, *num_chunks * sizeof(ImageChunk));
ImageChunk* curr = *chunks + (*num_chunks-1);
curr->start = pos;
// 'pos' is not the offset of the start of a gzip chunk, so scan // 'pos' is not the offset of the start of a gzip chunk, so scan
// forward until we find a gzip header. // forward until we find a gzip header.
curr->type = CHUNK_NORMAL; curr->type = CHUNK_NORMAL;
@@ -264,7 +505,13 @@ unsigned char* ReadImage(const char* filename,
* the chunk). Return 0 on success. * the chunk). Return 0 on success.
*/ */
int TryReconstruction(ImageChunk* chunk, unsigned char* out) { int TryReconstruction(ImageChunk* chunk, unsigned char* out) {
size_t p = chunk->gzip_header_len; size_t p = 0;
#if 0
fprintf(stderr, "trying %d %d %d %d %d\n",
chunk->level, chunk->method, chunk->windowBits,
chunk->memLevel, chunk->strategy);
#endif
z_stream strm; z_stream strm;
strm.zalloc = Z_NULL; strm.zalloc = Z_NULL;
@@ -281,7 +528,7 @@ int TryReconstruction(ImageChunk* chunk, unsigned char* out) {
ret = deflate(&strm, Z_FINISH); ret = deflate(&strm, Z_FINISH);
size_t have = BUFFER_SIZE - strm.avail_out; size_t have = BUFFER_SIZE - strm.avail_out;
if (memcmp(out, chunk->gzip_data+p, have) != 0) { if (memcmp(out, chunk->deflate_data+p, have) != 0) {
// mismatch; data isn't the same. // mismatch; data isn't the same.
deflateEnd(&strm); deflateEnd(&strm);
return -1; return -1;
@@ -289,7 +536,7 @@ int TryReconstruction(ImageChunk* chunk, unsigned char* out) {
p += have; p += have;
} while (ret != Z_STREAM_END); } while (ret != Z_STREAM_END);
deflateEnd(&strm); deflateEnd(&strm);
if (p + GZIP_FOOTER_LEN != chunk->gzip_len) { if (p != chunk->deflate_len) {
// mismatch; ran out of data before we should have. // mismatch; ran out of data before we should have.
return -1; return -1;
} }
@@ -302,9 +549,9 @@ int TryReconstruction(ImageChunk* chunk, unsigned char* out) {
* strategy fields in the chunk to the encoding parameters needed to * strategy fields in the chunk to the encoding parameters needed to
* produce the right output. Returns 0 on success. * produce the right output. Returns 0 on success.
*/ */
int ReconstructGzipChunk(ImageChunk* chunk) { int ReconstructDeflateChunk(ImageChunk* chunk) {
if (chunk->type != CHUNK_GZIP) { if (chunk->type != CHUNK_DEFLATE) {
fprintf(stderr, "attempt to reconstruct non-gzip chunk\n"); fprintf(stderr, "attempt to reconstruct non-deflate chunk\n");
return -1; return -1;
} }
@@ -329,27 +576,6 @@ int ReconstructGzipChunk(ImageChunk* chunk) {
return -1; return -1;
} }
/** Write a 4-byte value to f in little-endian order. */
void Write4(int value, FILE* f) {
fputc(value & 0xff, f);
fputc((value >> 8) & 0xff, f);
fputc((value >> 16) & 0xff, f);
fputc((value >> 24) & 0xff, f);
}
/** Write an 8-byte value to f in little-endian order. */
void Write8(long long value, FILE* f) {
fputc(value & 0xff, f);
fputc((value >> 8) & 0xff, f);
fputc((value >> 16) & 0xff, f);
fputc((value >> 24) & 0xff, f);
fputc((value >> 32) & 0xff, f);
fputc((value >> 40) & 0xff, f);
fputc((value >> 48) & 0xff, f);
fputc((value >> 56) & 0xff, f);
}
/* /*
* Given source and target chunks, compute a bsdiff patch between them * Given source and target chunks, compute a bsdiff patch between them
* by running bsdiff in a subprocess. Return the patch data, placing * by running bsdiff in a subprocess. Return the patch data, placing
@@ -357,6 +583,14 @@ void Write8(long long value, FILE* f) {
* program to be in the path. * program to be in the path.
*/ */
unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) { unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) {
if (tgt->type == CHUNK_NORMAL) {
if (tgt->len <= 160) {
tgt->type = CHUNK_RAW;
*size = tgt->len;
return tgt->data;
}
}
char stemp[] = "/tmp/imgdiff-src-XXXXXX"; char stemp[] = "/tmp/imgdiff-src-XXXXXX";
char ttemp[] = "/tmp/imgdiff-tgt-XXXXXX"; char ttemp[] = "/tmp/imgdiff-tgt-XXXXXX";
char ptemp[] = "/tmp/imgdiff-patch-XXXXXX"; char ptemp[] = "/tmp/imgdiff-patch-XXXXXX";
@@ -405,6 +639,17 @@ unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) {
} }
unsigned char* data = malloc(st.st_size); unsigned char* data = malloc(st.st_size);
if (tgt->type == CHUNK_NORMAL && tgt->len <= st.st_size) {
unlink(stemp);
unlink(ttemp);
unlink(ptemp);
tgt->type = CHUNK_RAW;
*size = tgt->len;
return tgt->data;
}
*size = st.st_size; *size = st.st_size;
f = fopen(ptemp, "rb"); f = fopen(ptemp, "rb");
@@ -422,6 +667,17 @@ unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) {
unlink(ttemp); unlink(ttemp);
unlink(ptemp); unlink(ptemp);
tgt->source_start = src->start;
switch (tgt->type) {
case CHUNK_NORMAL:
tgt->source_len = src->len;
break;
case CHUNK_DEFLATE:
tgt->source_len = src->deflate_len;
tgt->source_uncompressed_len = src->len;
break;
}
return data; return data;
} }
@@ -432,11 +688,12 @@ unsigned char* MakePatch(ImageChunk* src, ImageChunk* tgt, size_t* size) {
* where some gzip chunks are reconstructible but others aren't (by * where some gzip chunks are reconstructible but others aren't (by
* treating the ones that aren't as normal chunks). * treating the ones that aren't as normal chunks).
*/ */
void ChangeGzipChunkToNormal(ImageChunk* ch) { void ChangeDeflateChunkToNormal(ImageChunk* ch) {
if (ch->type != CHUNK_DEFLATE) return;
ch->type = CHUNK_NORMAL; ch->type = CHUNK_NORMAL;
free(ch->data); free(ch->data);
ch->data = ch->gzip_data; ch->data = ch->deflate_data;
ch->len = ch->gzip_len; ch->len = ch->deflate_len;
} }
/* /*
@@ -450,9 +707,9 @@ int AreChunksEqual(ImageChunk* a, ImageChunk* b) {
case CHUNK_NORMAL: case CHUNK_NORMAL:
return a->len == b->len && memcmp(a->data, b->data, a->len) == 0; return a->len == b->len && memcmp(a->data, b->data, a->len) == 0;
case CHUNK_GZIP: case CHUNK_DEFLATE:
return a->gzip_len == b->gzip_len && return a->deflate_len == b->deflate_len &&
memcmp(a->gzip_data, b->gzip_data, a->gzip_len) == 0; memcmp(a->deflate_data, b->deflate_data, a->deflate_len) == 0;
default: default:
fprintf(stderr, "unknown chunk type %d\n", a->type); fprintf(stderr, "unknown chunk type %d\n", a->type);
@@ -462,7 +719,7 @@ int AreChunksEqual(ImageChunk* a, ImageChunk* b) {
/* /*
* Look for runs of adjacent normal chunks and compress them down into * Look for runs of adjacent normal chunks and compress them down into
* a single chunk. (Such runs can be produced when gzip chunks are * a single chunk. (Such runs can be produced when deflate chunks are
* changed to normal chunks.) * changed to normal chunks.)
*/ */
void MergeAdjacentNormalChunks(ImageChunk* chunks, int* num_chunks) { void MergeAdjacentNormalChunks(ImageChunk* chunks, int* num_chunks) {
@@ -476,7 +733,7 @@ void MergeAdjacentNormalChunks(ImageChunk* chunks, int* num_chunks) {
// that constitute a solid block of data (ie, each chunk begins // that constitute a solid block of data (ie, each chunk begins
// where the previous one ended). // where the previous one ended).
for (in_end = in_start+1; for (in_end = in_start+1;
in_end < num_chunks && chunks[in_end].type == CHUNK_NORMAL && in_end < *num_chunks && chunks[in_end].type == CHUNK_NORMAL &&
(chunks[in_end].start == (chunks[in_end].start ==
chunks[in_end-1].start + chunks[in_end-1].len && chunks[in_end-1].start + chunks[in_end-1].len &&
chunks[in_end].data == chunks[in_end].data ==
@@ -485,11 +742,16 @@ void MergeAdjacentNormalChunks(ImageChunk* chunks, int* num_chunks) {
} }
if (in_end == in_start+1) { if (in_end == in_start+1) {
#if 0
printf("chunk %d is now %d\n", in_start, out);
#endif
if (out != in_start) { if (out != in_start) {
memcpy(chunks+out, chunks+in_start, sizeof(ImageChunk)); memcpy(chunks+out, chunks+in_start, sizeof(ImageChunk));
} }
} else { } else {
printf("collapse normal chunks %d - %d\n", in_start, in_end-1); #if 0
printf("collapse normal chunks %d-%d into %d\n", in_start, in_end-1, out);
#endif
// Merge chunks [in_start, in_end-1] into one chunk. Since the // Merge chunks [in_start, in_end-1] into one chunk. Since the
// data member of each chunk is just a pointer into an in-memory // data member of each chunk is just a pointer into an in-memory
@@ -510,93 +772,159 @@ void MergeAdjacentNormalChunks(ImageChunk* chunks, int* num_chunks) {
*num_chunks = out; *num_chunks = out;
} }
ImageChunk* FindChunkByName(const char* name,
ImageChunk* chunks, int num_chunks) {
int i;
for (i = 0; i < num_chunks; ++i) {
if (chunks[i].type == CHUNK_DEFLATE && chunks[i].filename &&
strcmp(name, chunks[i].filename) == 0) {
return chunks+i;
}
}
return NULL;
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
if (argc != 4) { if (argc != 4 && argc != 5) {
fprintf(stderr, "usage: %s <src-img> <tgt-img> <patch-file>\n", argv[0]); usage:
fprintf(stderr, "usage: %s [-z] <src-img> <tgt-img> <patch-file>\n",
argv[0]);
return 2; return 2;
} }
int zip_mode = 0;
if (strcmp(argv[1], "-z") == 0) {
zip_mode = 1;
--argc;
++argv;
}
int num_src_chunks; int num_src_chunks;
ImageChunk* src_chunks; ImageChunk* src_chunks;
if (ReadImage(argv[1], &num_src_chunks, &src_chunks) == NULL) {
fprintf(stderr, "failed to break apart source image\n");
return 1;
}
int num_tgt_chunks; int num_tgt_chunks;
ImageChunk* tgt_chunks; ImageChunk* tgt_chunks;
if (ReadImage(argv[2], &num_tgt_chunks, &tgt_chunks) == NULL) {
fprintf(stderr, "failed to break apart target image\n");
return 1;
}
// Verify that the source and target images have the same chunk
// structure (ie, the same sequence of gzip and normal chunks).
if (num_src_chunks != num_tgt_chunks) {
fprintf(stderr, "source and target don't have same number of chunks!\n");
return 1;
}
int i; int i;
for (i = 0; i < num_src_chunks; ++i) {
if (src_chunks[i].type != tgt_chunks[i].type) { if (zip_mode) {
fprintf(stderr, "source and target don't have same chunk " if (ReadZip(argv[1], &num_src_chunks, &src_chunks, 1) == NULL) {
"structure! (chunk %d)\n", i); fprintf(stderr, "failed to break apart source zip file\n");
return 1; return 1;
} }
if (ReadZip(argv[2], &num_tgt_chunks, &tgt_chunks, 0) == NULL) {
fprintf(stderr, "failed to break apart target zip file\n");
return 1;
}
} else {
if (ReadImage(argv[1], &num_src_chunks, &src_chunks) == NULL) {
fprintf(stderr, "failed to break apart source image\n");
return 1;
}
if (ReadImage(argv[2], &num_tgt_chunks, &tgt_chunks) == NULL) {
fprintf(stderr, "failed to break apart target image\n");
return 1;
}
// Verify that the source and target images have the same chunk
// structure (ie, the same sequence of deflate and normal chunks).
if (num_src_chunks != num_tgt_chunks) {
fprintf(stderr, "source and target don't have same number of chunks!\n");
return 1;
}
for (i = 0; i < num_src_chunks; ++i) {
if (src_chunks[i].type != tgt_chunks[i].type) {
fprintf(stderr, "source and target don't have same chunk "
"structure! (chunk %d)\n", i);
return 1;
}
}
} }
for (i = 0; i < num_tgt_chunks; ++i) { for (i = 0; i < num_tgt_chunks; ++i) {
if (tgt_chunks[i].type == CHUNK_GZIP) { if (tgt_chunks[i].type == CHUNK_DEFLATE) {
// Confirm that given the uncompressed chunk data in the target, we // Confirm that given the uncompressed chunk data in the target, we
// can recompress it and get exactly the same bits as are in the // can recompress it and get exactly the same bits as are in the
// input target image. If this fails, treat the chunk as a normal // input target image. If this fails, treat the chunk as a normal
// non-gzipped chunk. // non-deflated chunk.
if (ReconstructGzipChunk(tgt_chunks+i) < 0) { if (ReconstructDeflateChunk(tgt_chunks+i) < 0) {
printf("failed to reconstruct target gzip chunk %d; " printf("failed to reconstruct target deflate chunk %d [%s]; "
"treating as normal chunk\n", i); "treating as normal\n", i, tgt_chunks[i].filename);
ChangeGzipChunkToNormal(tgt_chunks+i); ChangeDeflateChunkToNormal(tgt_chunks+i);
ChangeGzipChunkToNormal(src_chunks+i); if (zip_mode) {
ImageChunk* src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks);
if (src) {
ChangeDeflateChunkToNormal(src);
}
} else {
ChangeDeflateChunkToNormal(src_chunks+i);
}
continue; continue;
} else {
printf("reconstructed target gzip chunk %d\n", i);
} }
// If two gzip chunks are identical (eg, the kernel has not // If two deflate chunks are identical (eg, the kernel has not
// changed between two builds), treat them as normal chunks. // changed between two builds), treat them as normal chunks.
// This makes applypatch much faster -- it can apply a trivial // This makes applypatch much faster -- it can apply a trivial
// patch to the compressed data, rather than uncompressing and // patch to the compressed data, rather than uncompressing and
// recompressing to apply the trivial patch to the uncompressed // recompressing to apply the trivial patch to the uncompressed
// data. // data.
if (AreChunksEqual(tgt_chunks+i, src_chunks+i)) { ImageChunk* src;
printf("source and target chunk %d are identical; " if (zip_mode) {
"treating as normal chunk\n", i); src = FindChunkByName(tgt_chunks[i].filename, src_chunks, num_src_chunks);
ChangeGzipChunkToNormal(tgt_chunks+i); } else {
ChangeGzipChunkToNormal(src_chunks+i); src = src_chunks+i;
}
if (src == NULL || AreChunksEqual(tgt_chunks+i, src)) {
ChangeDeflateChunkToNormal(tgt_chunks+i);
if (src) {
ChangeDeflateChunkToNormal(src);
}
} }
} }
} }
// If we changed any gzip chunks to normal chunks, we can simplify // Merging neighboring normal chunks.
// the patch by merging neighboring normal chunks. if (zip_mode) {
MergeAdjacentNormalChunks(src_chunks, &num_src_chunks); // For zips, we only need to do this to the target: deflated
MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks); // chunks are matched via filename, and normal chunks are patched
if (num_src_chunks != num_tgt_chunks) { // using the entire source file as the source.
// This shouldn't happen. MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
fprintf(stderr, "merging normal chunks went awry\n"); } else {
return 1; // For images, we need to maintain the parallel structure of the
// chunk lists, so do the merging in both the source and target
// lists.
MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
MergeAdjacentNormalChunks(src_chunks, &num_src_chunks);
if (num_src_chunks != num_tgt_chunks) {
// This shouldn't happen.
fprintf(stderr, "merging normal chunks went awry\n");
return 1;
}
} }
// Compute bsdiff patches for each chunk's data (the uncompressed // Compute bsdiff patches for each chunk's data (the uncompressed
// data, in the case of gzip chunks). // data, in the case of deflate chunks).
unsigned char** patch_data = malloc(num_src_chunks * sizeof(unsigned char*));
size_t* patch_size = malloc(num_src_chunks * sizeof(size_t));
for (i = 0; i < num_src_chunks; ++i) {
patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i);
printf("patch %d is %d bytes (of %d)\n", i, patch_size[i],
tgt_chunks[i].type == CHUNK_NORMAL ? tgt_chunks[i].len : tgt_chunks[i].gzip_len);
printf("Construct patches for %d chunks...\n", num_tgt_chunks);
unsigned char** patch_data = malloc(num_tgt_chunks * sizeof(unsigned char*));
size_t* patch_size = malloc(num_tgt_chunks * sizeof(size_t));
for (i = 0; i < num_tgt_chunks; ++i) {
if (zip_mode) {
ImageChunk* src;
if (tgt_chunks[i].type == CHUNK_DEFLATE &&
(src = FindChunkByName(tgt_chunks[i].filename, src_chunks,
num_src_chunks))) {
patch_data[i] = MakePatch(src, tgt_chunks+i, patch_size+i);
} else {
patch_data[i] = MakePatch(src_chunks, tgt_chunks+i, patch_size+i);
}
} else {
patch_data[i] = MakePatch(src_chunks+i, tgt_chunks+i, patch_size+i);
}
printf("patch %3d is %d bytes (of %d)\n",
i, patch_size[i], tgt_chunks[i].source_len);
} }
// Figure out how big the imgdiff file header is going to be, so // Figure out how big the imgdiff file header is going to be, so
@@ -604,10 +932,18 @@ int main(int argc, char** argv) {
// within the file. // within the file.
size_t total_header_size = 12; size_t total_header_size = 12;
for (i = 0; i < num_src_chunks; ++i) { for (i = 0; i < num_tgt_chunks; ++i) {
total_header_size += 4 + 8*3; total_header_size += 4;
if (src_chunks[i].type == CHUNK_GZIP) { switch (tgt_chunks[i].type) {
total_header_size += 8*2 + 4*6 + tgt_chunks[i].gzip_header_len + 8; case CHUNK_NORMAL:
total_header_size += 8*3;
break;
case CHUNK_DEFLATE:
total_header_size += 8*5 + 4*5;
break;
case CHUNK_RAW:
total_header_size += 4 + patch_size[i];
break;
} }
} }
@@ -617,35 +953,53 @@ int main(int argc, char** argv) {
// Write out the headers. // Write out the headers.
fwrite("IMGDIFF1", 1, 8, f); fwrite("IMGDIFF2", 1, 8, f);
Write4(num_src_chunks, f); Write4(num_tgt_chunks, f);
for (i = 0; i < num_tgt_chunks; ++i) { for (i = 0; i < num_tgt_chunks; ++i) {
Write4(tgt_chunks[i].type, f); Write4(tgt_chunks[i].type, f);
Write8(src_chunks[i].start, f);
Write8(src_chunks[i].type == CHUNK_NORMAL ? src_chunks[i].len :
(src_chunks[i].gzip_len + src_chunks[i].gzip_header_len + 8), f);
Write8(offset, f);
if (tgt_chunks[i].type == CHUNK_GZIP) { switch (tgt_chunks[i].type) {
Write8(src_chunks[i].len, f); case CHUNK_NORMAL:
Write8(tgt_chunks[i].len, f); printf("chunk %3d: normal (%10d, %10d) %10d\n", i,
Write4(tgt_chunks[i].level, f); tgt_chunks[i].start, tgt_chunks[i].len, patch_size[i]);
Write4(tgt_chunks[i].method, f); Write8(tgt_chunks[i].source_start, f);
Write4(tgt_chunks[i].windowBits, f); Write8(tgt_chunks[i].source_len, f);
Write4(tgt_chunks[i].memLevel, f); Write8(offset, f);
Write4(tgt_chunks[i].strategy, f); offset += patch_size[i];
Write4(tgt_chunks[i].gzip_header_len, f); break;
fwrite(tgt_chunks[i].gzip_header, 1, tgt_chunks[i].gzip_header_len, f);
fwrite(tgt_chunks[i].gzip_footer, 1, GZIP_FOOTER_LEN, f); case CHUNK_DEFLATE:
printf("chunk %3d: deflate (%10d, %10d) %10d %s\n", i,
tgt_chunks[i].start, tgt_chunks[i].deflate_len, patch_size[i],
tgt_chunks[i].filename);
Write8(tgt_chunks[i].source_start, f);
Write8(tgt_chunks[i].source_len, f);
Write8(offset, f);
Write8(tgt_chunks[i].source_uncompressed_len, f);
Write8(tgt_chunks[i].len, f);
Write4(tgt_chunks[i].level, f);
Write4(tgt_chunks[i].method, f);
Write4(tgt_chunks[i].windowBits, f);
Write4(tgt_chunks[i].memLevel, f);
Write4(tgt_chunks[i].strategy, f);
offset += patch_size[i];
break;
case CHUNK_RAW:
printf("chunk %3d: raw (%10d, %10d)\n", i,
tgt_chunks[i].start, tgt_chunks[i].len);
Write4(patch_size[i], f);
fwrite(patch_data[i], 1, patch_size[i], f);
break;
} }
offset += patch_size[i];
} }
// Append each chunk's bsdiff patch, in order. // Append each chunk's bsdiff patch, in order.
for (i = 0; i < num_tgt_chunks; ++i) { for (i = 0; i < num_tgt_chunks; ++i) {
fwrite(patch_data[i], 1, patch_size[i], f); if (tgt_chunks[i].type != CHUNK_RAW) {
fwrite(patch_data[i], 1, patch_size[i], f);
}
} }
fclose(f); fclose(f);

View File

@@ -15,8 +15,10 @@
*/ */
// Image patch chunk types // Image patch chunk types
#define CHUNK_NORMAL 0 #define CHUNK_NORMAL 0
#define CHUNK_GZIP 1 #define CHUNK_GZIP 1 // version 1 only
#define CHUNK_DEFLATE 2 // version 2 only
#define CHUNK_RAW 3 // version 2 only
// The gzip header size is actually variable, but we currently don't // The gzip header size is actually variable, but we currently don't
// support gzipped data with any of the optional fields, so for now it // support gzipped data with any of the optional fields, so for now it

118
tools/applypatch/imgdiff_test.sh Executable file
View File

@@ -0,0 +1,118 @@
#!/bin/bash
#
# A script for testing imgdiff/applypatch. It takes two full OTA
# packages as arguments. It generates (on the host) patches for all
# the zip/jar/apk files they have in common, as well as boot and
# recovery images. It then applies the patches on the device (or
# emulator) and checks that the resulting file is correct.
EMULATOR_PORT=5580
# set to 0 to use a device instead
USE_EMULATOR=0
# where on the device to do all the patching.
WORK_DIR=/data/local/tmp
START_OTA_PACKAGE=$1
END_OTA_PACKAGE=$2
# ------------------------
tmpdir=$(mktemp -d)
if [ "$USE_EMULATOR" == 1 ]; then
emulator -wipe-data -noaudio -no-window -port $EMULATOR_PORT &
pid_emulator=$!
ADB="adb -s emulator-$EMULATOR_PORT "
else
ADB="adb -d "
fi
echo "waiting to connect to device"
$ADB wait-for-device
# run a command on the device; exit with the exit status of the device
# command.
run_command() {
$ADB shell "$@" \; echo \$? | awk '{if (b) {print a}; a=$0; b=1} END {exit a}'
}
testname() {
echo
echo "$1"...
testname="$1"
}
fail() {
echo
echo FAIL: $testname
echo
[ "$open_pid" == "" ] || kill $open_pid
[ "$pid_emulator" == "" ] || kill $pid_emulator
exit 1
}
sha1() {
sha1sum $1 | awk '{print $1}'
}
size() {
stat -c %s $1 | tr -d '\n'
}
cleanup() {
# not necessary if we're about to kill the emulator, but nice for
# running on real devices or already-running emulators.
testname "removing test files"
run_command rm $WORK_DIR/applypatch
run_command rm $WORK_DIR/source
run_command rm $WORK_DIR/target
run_command rm $WORK_DIR/patch
[ "$pid_emulator" == "" ] || kill $pid_emulator
rm -rf $tmpdir
}
$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch
patch_and_apply() {
local fn=$1
shift
unzip -p $START_OTA_PACKAGE $fn > $tmpdir/source
unzip -p $END_OTA_PACKAGE $fn > $tmpdir/target
imgdiff "$@" $tmpdir/source $tmpdir/target $tmpdir/patch
bsdiff $tmpdir/source $tmpdir/target $tmpdir/patch.bs
echo "patch for $fn is $(size $tmpdir/patch) [of $(size $tmpdir/target)] ($(size $tmpdir/patch.bs) with bsdiff)"
echo "$fn $(size $tmpdir/patch) of $(size $tmpdir/target) bsdiff $(size $tmpdir/patch.bs)" >> /tmp/stats.txt
$ADB push $tmpdir/source $WORK_DIR/source || fail "source push failed"
run_command rm /data/local/tmp/target
$ADB push $tmpdir/patch $WORK_DIR/patch || fail "patch push failed"
run_command /data/local/tmp/applypatch /data/local/tmp/source \
/data/local/tmp/target $(sha1 $tmpdir/target) $(size $tmpdir/target) \
$(sha1 $tmpdir/source):/data/local/tmp/patch \
|| fail "applypatch of $fn failed"
$ADB pull /data/local/tmp/target $tmpdir/result
diff -q $tmpdir/target $tmpdir/result || fail "patch output not correct!"
}
# --------------- basic execution ----------------------
for i in $((zipinfo -1 $START_OTA_PACKAGE; zipinfo -1 $END_OTA_PACKAGE) | \
sort | uniq -d | egrep -e '[.](apk|jar|zip)$'); do
patch_and_apply $i -z
done
patch_and_apply boot.img
patch_and_apply system/recovery.img
# --------------- cleanup ----------------------
cleanup
echo
echo PASS
echo

View File

@@ -27,24 +27,7 @@
#include "mincrypt/sha.h" #include "mincrypt/sha.h"
#include "applypatch.h" #include "applypatch.h"
#include "imgdiff.h" #include "imgdiff.h"
#include "utils.h"
int Read4(unsigned char* p) {
return (int)(((unsigned int)p[3] << 24) |
((unsigned int)p[2] << 16) |
((unsigned int)p[1] << 8) |
(unsigned int)p[0]);
}
long long Read8(unsigned char* p) {
return (long long)(((unsigned long long)p[7] << 56) |
((unsigned long long)p[6] << 48) |
((unsigned long long)p[5] << 40) |
((unsigned long long)p[4] << 32) |
((unsigned long long)p[3] << 24) |
((unsigned long long)p[2] << 16) |
((unsigned long long)p[1] << 8) |
(unsigned long long)p[0]);
}
/* /*
* Apply the patch given in 'patch_filename' to the source data given * Apply the patch given in 'patch_filename' to the source data given
@@ -67,7 +50,10 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
return -1; return -1;
} }
if (memcmp(header, "IMGDIFF1", 8) != 0) { // IMGDIFF1 uses CHUNK_NORMAL and CHUNK_GZIP.
// IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW.
if (memcmp(header, "IMGDIFF", 7) != 0 ||
(header[7] != '1' && header[7] != '2')) {
fprintf(stderr, "corrupt patch file header (magic number)\n"); fprintf(stderr, "corrupt patch file header (magic number)\n");
return -1; return -1;
} }
@@ -76,48 +62,67 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
int i; int i;
for (i = 0; i < num_chunks; ++i) { for (i = 0; i < num_chunks; ++i) {
// each chunk's header record starts with 28 bytes (4 + 8*3). // each chunk's header record starts with 4 bytes.
unsigned char chunk[28]; unsigned char chunk[4];
if (fread(chunk, 1, 28, f) != 28) { if (fread(chunk, 1, 4, f) != 4) {
fprintf(stderr, "failed to read chunk %d record\n", i); fprintf(stderr, "failed to read chunk %d record\n", i);
return -1; return -1;
} }
int type = Read4(chunk); int type = Read4(chunk);
size_t src_start = Read8(chunk+4);
size_t src_len = Read8(chunk+12);
size_t patch_offset = Read8(chunk+20);
if (type == CHUNK_NORMAL) { if (type == CHUNK_NORMAL) {
unsigned char normal_header[24];
if (fread(normal_header, 1, 24, f) != 24) {
fprintf(stderr, "failed to read chunk %d normal header data\n", i);
return -1;
}
size_t src_start = Read8(normal_header);
size_t src_len = Read8(normal_header+8);
size_t patch_offset = Read8(normal_header+16);
fprintf(stderr, "CHUNK %d: normal patch offset %d\n", i, patch_offset); fprintf(stderr, "CHUNK %d: normal patch offset %d\n", i, patch_offset);
ApplyBSDiffPatch(old_data + src_start, src_len, ApplyBSDiffPatch(old_data + src_start, src_len,
patch_filename, patch_offset, patch_filename, patch_offset,
output, ctx); output, ctx);
} else if (type == CHUNK_GZIP) { } else if (type == CHUNK_GZIP) {
fprintf(stderr, "CHUNK %d: gzip patch offset %d\n", i, patch_offset); // This branch is basically a duplicate of the CHUNK_DEFLATE
// branch, with a bit of extra processing for the gzip header
// and footer. I've avoided factoring the common code out since
// this branch will just be deleted when we drop support for
// IMGDIFF1.
// gzip chunks have an additional 40 + gzip_header_len + 8 bytes // gzip chunks have an additional 64 + gzip_header_len + 8 bytes
// in their chunk header. // in their chunk header.
unsigned char* gzip = malloc(40); unsigned char* gzip = malloc(64);
if (fread(gzip, 1, 40, f) != 40) { if (fread(gzip, 1, 64, f) != 64) {
fprintf(stderr, "failed to read chunk %d initial gzip data\n", i); fprintf(stderr, "failed to read chunk %d initial gzip header data\n",
i);
return -1; return -1;
} }
size_t gzip_header_len = Read4(gzip+36); size_t gzip_header_len = Read4(gzip+60);
gzip = realloc(gzip, 40 + gzip_header_len + 8); gzip = realloc(gzip, 64 + gzip_header_len + 8);
if (fread(gzip+40, 1, gzip_header_len+8, f) != gzip_header_len+8) { if (fread(gzip+64, 1, gzip_header_len+8, f) != gzip_header_len+8) {
fprintf(stderr, "failed to read chunk %d remaining gzip data\n", i); fprintf(stderr, "failed to read chunk %d remaining gzip header data\n",
i);
return -1; return -1;
} }
size_t expanded_len = Read8(gzip); size_t src_start = Read8(gzip);
size_t target_len = Read8(gzip); size_t src_len = Read8(gzip+8);
int gz_level = Read4(gzip+16); size_t patch_offset = Read8(gzip+16);
int gz_method = Read4(gzip+20);
int gz_windowBits = Read4(gzip+24); size_t expanded_len = Read8(gzip+24);
int gz_memLevel = Read4(gzip+28); size_t target_len = Read8(gzip+32);
int gz_strategy = Read4(gzip+32); int gz_level = Read4(gzip+40);
int gz_method = Read4(gzip+44);
int gz_windowBits = Read4(gzip+48);
int gz_memLevel = Read4(gzip+52);
int gz_strategy = Read4(gzip+56);
fprintf(stderr, "CHUNK %d: gzip patch offset %d\n", i, patch_offset);
// Decompress the source data; the chunk header tells us exactly // Decompress the source data; the chunk header tells us exactly
// how big we expect it to be when decompressed. // how big we expect it to be when decompressed.
@@ -173,8 +178,8 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
// Now compress the target data and append it to the output. // Now compress the target data and append it to the output.
// start with the gzip header. // start with the gzip header.
fwrite(gzip+40, 1, gzip_header_len, output); fwrite(gzip+64, 1, gzip_header_len, output);
SHA_update(ctx, gzip+40, gzip_header_len); SHA_update(ctx, gzip+64, gzip_header_len);
// we're done with the expanded_source data buffer, so we'll // we're done with the expanded_source data buffer, so we'll
// reuse that memory to receive the output of deflate. // reuse that memory to receive the output of deflate.
@@ -212,12 +217,143 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
deflateEnd(&strm); deflateEnd(&strm);
// lastly, the gzip footer. // lastly, the gzip footer.
fwrite(gzip+40+gzip_header_len, 1, 8, output); fwrite(gzip+64+gzip_header_len, 1, 8, output);
SHA_update(ctx, gzip+40+gzip_header_len, 8); SHA_update(ctx, gzip+64+gzip_header_len, 8);
free(temp_data); free(temp_data);
free(uncompressed_target_data); free(uncompressed_target_data);
free(gzip); free(gzip);
} else if (type == CHUNK_RAW) {
unsigned char raw_header[4];
if (fread(raw_header, 1, 4, f) != 4) {
fprintf(stderr, "failed to read chunk %d raw header data\n", i);
return -1;
}
size_t data_len = Read4(raw_header);
fprintf(stderr, "CHUNK %d: raw data %d\n", i, data_len);
unsigned char* temp = malloc(data_len);
if (fread(temp, 1, data_len, f) != data_len) {
fprintf(stderr, "failed to read chunk %d raw data\n", i);
return -1;
}
SHA_update(ctx, temp, data_len);
if (fwrite(temp, 1, data_len, output) != data_len) {
fprintf(stderr, "failed to write chunk %d raw data\n", i);
return -1;
}
} else if (type == CHUNK_DEFLATE) {
// deflate chunks have an additional 60 bytes in their chunk header.
unsigned char deflate_header[60];
if (fread(deflate_header, 1, 60, f) != 60) {
fprintf(stderr, "failed to read chunk %d deflate header data\n", i);
return -1;
}
size_t src_start = Read8(deflate_header);
size_t src_len = Read8(deflate_header+8);
size_t patch_offset = Read8(deflate_header+16);
size_t expanded_len = Read8(deflate_header+24);
size_t target_len = Read8(deflate_header+32);
int level = Read4(deflate_header+40);
int method = Read4(deflate_header+44);
int windowBits = Read4(deflate_header+48);
int memLevel = Read4(deflate_header+52);
int strategy = Read4(deflate_header+56);
fprintf(stderr, "CHUNK %d: deflate patch offset %d\n", i, patch_offset);
// Decompress the source data; the chunk header tells us exactly
// how big we expect it to be when decompressed.
unsigned char* expanded_source = malloc(expanded_len);
if (expanded_source == NULL) {
fprintf(stderr, "failed to allocate %d bytes for expanded_source\n",
expanded_len);
return -1;
}
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = src_len;
strm.next_in = (unsigned char*)(old_data + src_start);
strm.avail_out = expanded_len;
strm.next_out = expanded_source;
int ret;
ret = inflateInit2(&strm, -15);
if (ret != Z_OK) {
fprintf(stderr, "failed to init source inflation: %d\n", ret);
return -1;
}
// Because we've provided enough room to accommodate the output
// data, we expect one call to inflate() to suffice.
ret = inflate(&strm, Z_SYNC_FLUSH);
if (ret != Z_STREAM_END) {
fprintf(stderr, "source inflation returned %d\n", ret);
return -1;
}
// We should have filled the output buffer exactly.
if (strm.avail_out != 0) {
fprintf(stderr, "source inflation short by %d bytes\n", strm.avail_out);
return -1;
}
inflateEnd(&strm);
// Next, apply the bsdiff patch (in memory) to the uncompressed
// data.
unsigned char* uncompressed_target_data;
ssize_t uncompressed_target_size;
if (ApplyBSDiffPatchMem(expanded_source, expanded_len,
patch_filename, patch_offset,
&uncompressed_target_data,
&uncompressed_target_size) != 0) {
return -1;
}
// Now compress the target data and append it to the output.
// we're done with the expanded_source data buffer, so we'll
// reuse that memory to receive the output of deflate.
unsigned char* temp_data = expanded_source;
ssize_t temp_size = expanded_len;
if (temp_size < 32768) {
// ... unless the buffer is too small, in which case we'll
// allocate a fresh one.
free(temp_data);
temp_data = malloc(32768);
temp_size = 32768;
}
// now the deflate stream
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = uncompressed_target_size;
strm.next_in = uncompressed_target_data;
ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy);
do {
strm.avail_out = temp_size;
strm.next_out = temp_data;
ret = deflate(&strm, Z_FINISH);
size_t have = temp_size - strm.avail_out;
if (fwrite(temp_data, 1, have, output) != have) {
fprintf(stderr, "failed to write %d compressed bytes to output\n",
have);
return -1;
}
SHA_update(ctx, temp_data, have);
} while (ret != Z_STREAM_END);
deflateEnd(&strm);
free(temp_data);
free(uncompressed_target_data);
} else { } else {
fprintf(stderr, "patch chunk %d is unknown type %d\n", i, type); fprintf(stderr, "patch chunk %d is unknown type %d\n", i, type);
return -1; return -1;

62
tools/applypatch/utils.c Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include "utils.h"
/** Write a 4-byte value to f in little-endian order. */
void Write4(int value, FILE* f) {
fputc(value & 0xff, f);
fputc((value >> 8) & 0xff, f);
fputc((value >> 16) & 0xff, f);
fputc((value >> 24) & 0xff, f);
}
/** Write an 8-byte value to f in little-endian order. */
void Write8(long long value, FILE* f) {
fputc(value & 0xff, f);
fputc((value >> 8) & 0xff, f);
fputc((value >> 16) & 0xff, f);
fputc((value >> 24) & 0xff, f);
fputc((value >> 32) & 0xff, f);
fputc((value >> 40) & 0xff, f);
fputc((value >> 48) & 0xff, f);
fputc((value >> 56) & 0xff, f);
}
int Read2(unsigned char* p) {
return (int)(((unsigned int)p[1] << 8) |
(unsigned int)p[0]);
}
int Read4(unsigned char* p) {
return (int)(((unsigned int)p[3] << 24) |
((unsigned int)p[2] << 16) |
((unsigned int)p[1] << 8) |
(unsigned int)p[0]);
}
long long Read8(unsigned char* p) {
return (long long)(((unsigned long long)p[7] << 56) |
((unsigned long long)p[6] << 48) |
((unsigned long long)p[5] << 40) |
((unsigned long long)p[4] << 32) |
((unsigned long long)p[3] << 24) |
((unsigned long long)p[2] << 16) |
((unsigned long long)p[1] << 8) |
(unsigned long long)p[0]);
}

30
tools/applypatch/utils.h Normal file
View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _BUILD_TOOLS_APPLYPATCH_UTILS_H
#define _BUILD_TOOLS_APPLYPATCH_UTILS_H
#include <stdio.h>
// Read and write little-endian values of various sizes.
void Write4(int value, FILE* f);
void Write8(long long value, FILE* f);
int Read2(unsigned char* p);
int Read4(unsigned char* p);
long long Read8(unsigned char* p);
#endif // _BUILD_TOOLS_APPLYPATCH_UTILS_H