diff options
Diffstat (limited to 'applypatch')
-rw-r--r-- | applypatch/applypatch.cpp | 45 | ||||
-rwxr-xr-x | applypatch/applypatch.sh | 350 | ||||
-rw-r--r-- | applypatch/applypatch_modes.cpp | 18 | ||||
-rw-r--r-- | applypatch/bspatch.cpp | 48 | ||||
-rwxr-xr-x | applypatch/imgdiff_test.sh | 118 | ||||
-rw-r--r-- | applypatch/imgpatch.cpp | 169 | ||||
-rw-r--r-- | applypatch/include/applypatch/applypatch.h | 17 | ||||
-rw-r--r-- | applypatch/include/applypatch/imgpatch.h | 9 | ||||
-rw-r--r-- | applypatch/testdata/new.file | bin | 1388877 -> 0 bytes | |||
-rw-r--r-- | applypatch/testdata/old.file | bin | 1348051 -> 0 bytes | |||
-rw-r--r-- | applypatch/testdata/patch.bsdiff | bin | 57476 -> 0 bytes |
11 files changed, 161 insertions, 613 deletions
diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp index 7be3fdbde..51bf3932a 100644 --- a/applypatch/applypatch.cpp +++ b/applypatch/applypatch.cpp @@ -27,6 +27,7 @@ #include <sys/types.h> #include <unistd.h> +#include <functional> #include <memory> #include <string> #include <utility> @@ -42,7 +43,7 @@ #include "print_sha1.h" static int LoadPartitionContents(const std::string& filename, FileContents* file); -static ssize_t FileSink(const unsigned char* data, ssize_t len, void* token); +static size_t FileSink(const unsigned char* data, size_t len, int fd); static int GenerateTarget(const FileContents& source_file, const std::unique_ptr<Value>& patch, const std::string& target_filename, const uint8_t target_sha1[SHA_DIGEST_LENGTH], const Value* bonus_data); @@ -194,8 +195,8 @@ int SaveFileContents(const char* filename, const FileContents* file) { return -1; } - ssize_t bytes_written = FileSink(file->data.data(), file->data.size(), &fd); - if (bytes_written != static_cast<ssize_t>(file->data.size())) { + size_t bytes_written = FileSink(file->data.data(), file->data.size(), fd); + if (bytes_written != file->data.size()) { printf("short write of \"%s\" (%zd bytes of %zu): %s\n", filename, bytes_written, file->data.size(), strerror(errno)); return -1; @@ -433,25 +434,17 @@ int ShowLicenses() { return 0; } -ssize_t FileSink(const unsigned char* data, ssize_t len, void* token) { - int fd = *static_cast<int*>(token); - ssize_t done = 0; - ssize_t wrote; - while (done < len) { - wrote = TEMP_FAILURE_RETRY(ota_write(fd, data+done, len-done)); - if (wrote == -1) { - printf("error writing %zd bytes: %s\n", (len-done), strerror(errno)); - return done; - } - done += wrote; +static size_t FileSink(const unsigned char* data, size_t len, int fd) { + size_t done = 0; + while (done < len) { + ssize_t wrote = TEMP_FAILURE_RETRY(ota_write(fd, data + done, len - done)); + if (wrote == -1) { + printf("error writing %zd bytes: %s\n", (len - done), strerror(errno)); + return done; } - return done; -} - -ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) { - std::string* s = static_cast<std::string*>(token); - s->append(reinterpret_cast<const char*>(data), len); - return len; + done += wrote; + } + return done; } // Return the amount of free space (in bytes) on the filesystem @@ -647,9 +640,11 @@ static int GenerateTarget(const FileContents& source_file, const std::unique_ptr } // We store the decoded output in memory. - SinkFn sink = MemorySink; std::string memory_sink_str; // Don't need to reserve space. - void* token = &memory_sink_str; + SinkFn sink = [&memory_sink_str](const unsigned char* data, size_t len) { + memory_sink_str.append(reinterpret_cast<const char*>(data), len); + return len; + }; SHA_CTX ctx; SHA1_Init(&ctx); @@ -657,10 +652,10 @@ static int GenerateTarget(const FileContents& source_file, const std::unique_ptr int result; if (use_bsdiff) { result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch.get(), 0, - sink, token, &ctx); + sink, &ctx); } else { result = ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch.get(), sink, - token, &ctx, bonus_data); + &ctx, bonus_data); } if (result != 0) { diff --git a/applypatch/applypatch.sh b/applypatch/applypatch.sh deleted file mode 100755 index 8ea68a1a9..000000000 --- a/applypatch/applypatch.sh +++ /dev/null @@ -1,350 +0,0 @@ -#!/bin/bash -# -# A test suite for applypatch. Run in a client where you have done -# envsetup, choosecombo, etc. -# -# DO NOT RUN THIS ON A DEVICE YOU CARE ABOUT. It will mess up your -# system partition. -# -# -# TODO: find some way to get this run regularly along with the rest of -# the tests. - -EMULATOR_PORT=5580 -DATA_DIR=$ANDROID_BUILD_TOP/bootable/recovery/applypatch/testdata - -# This must be the filename that applypatch uses for its copies. -CACHE_TEMP_SOURCE=/cache/saved.file - -# Put all binaries and files here. We use /cache because it's a -# temporary filesystem in the emulator; it's created fresh each time -# the emulator starts. -WORK_DIR=/system - -# partition that WORK_DIR is located on, without the leading slash -WORK_FS=system - -# set to 0 to use a device instead -USE_EMULATOR=1 - -# ------------------------ - -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 -echo "device is available" -$ADB remount -# free up enough space on the system partition for the test to run. -$ADB shell rm -r /system/media - -# 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}' -} - -free_space() { - run_command df | awk "/$1/ {print gensub(/K/, \"\", \"g\", \$6)}" -} - -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/bloat.dat - run_command rm $WORK_DIR/old.file - run_command rm $WORK_DIR/foo - run_command rm $WORK_DIR/patch.bsdiff - run_command rm $WORK_DIR/applypatch - run_command rm $CACHE_TEMP_SOURCE - run_command rm /cache/bloat*.dat - - [ "$pid_emulator" == "" ] || kill $pid_emulator - - if [ $# == 0 ]; then - rm -rf $tmpdir - fi -} - -cleanup leave_tmp - -$ADB push $ANDROID_PRODUCT_OUT/system/bin/applypatch $WORK_DIR/applypatch - -BAD1_SHA1=$(printf "%040x" $RANDOM) -BAD2_SHA1=$(printf "%040x" $RANDOM) -OLD_SHA1=$(sha1 $DATA_DIR/old.file) -NEW_SHA1=$(sha1 $DATA_DIR/new.file) -NEW_SIZE=$(stat -c %s $DATA_DIR/new.file) - -# --------------- basic execution ---------------------- - -testname "usage message" -run_command $WORK_DIR/applypatch && fail - -testname "display license" -run_command $WORK_DIR/applypatch -l | grep -q -i copyright || fail - - -# --------------- check mode ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR - -testname "check mode single" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail - -testname "check mode multiple" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail - -testname "check mode failure" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail - -$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE -# put some junk in the old file -run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail - -testname "check mode cache (corrupted) single" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail - -testname "check mode cache (corrupted) multiple" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail - -testname "check mode cache (corrupted) failure" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail - -# remove the old file entirely -run_command rm $WORK_DIR/old.file - -testname "check mode cache (missing) single" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $OLD_SHA1 || fail - -testname "check mode cache (missing) multiple" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD1_SHA1 $OLD_SHA1 $BAD2_SHA1|| fail - -testname "check mode cache (missing) failure" -run_command $WORK_DIR/applypatch -c $WORK_DIR/old.file $BAD2_SHA1 $BAD1_SHA1 && fail - - -# --------------- apply patch ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR -$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR -echo hello > $tmpdir/foo -$ADB push $tmpdir/foo $WORK_DIR - -# Check that the partition has enough space to apply the patch without -# copying. If it doesn't, we'll be testing the low-space condition -# when we intend to test the not-low-space condition. -testname "apply patches (with enough space)" -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS." -if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then - echo "Not enough space on /$WORK_FS to patch test file." - echo - echo "This doesn't mean that applypatch is necessarily broken;" - echo "just that /$WORK_FS doesn't have enough free space to" - echo "properly run this test." - exit 1 -fi - -testname "apply bsdiff patch" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -testname "reapply bsdiff patch" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - - -# --------------- apply patch in new location ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR -$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR - -# Check that the partition has enough space to apply the patch without -# copying. If it doesn't, we'll be testing the low-space condition -# when we intend to test the not-low-space condition. -testname "apply patch to new location (with enough space)" -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS." -if (( free_kb * 1024 < NEW_SIZE * 3 / 2 )); then - echo "Not enough space on /$WORK_FS to patch test file." - echo - echo "This doesn't mean that applypatch is necessarily broken;" - echo "just that /$WORK_FS doesn't have enough free space to" - echo "properly run this test." - exit 1 -fi - -run_command rm $WORK_DIR/new.file -run_command rm $CACHE_TEMP_SOURCE - -testname "apply bsdiff patch to new location" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/new.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -testname "reapply bsdiff patch to new location" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/new.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE -# put some junk in the old file -run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail - -testname "apply bsdiff patch to new location with corrupted source" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo || fail -$ADB pull $WORK_DIR/new.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -# put some junk in the cache copy, too -run_command dd if=/dev/urandom of=$CACHE_TEMP_SOURCE count=100 bs=1024 || fail - -run_command rm $WORK_DIR/new.file -testname "apply bsdiff patch to new location with corrupted source and copy (no new file)" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail - -# put some junk in the new file -run_command dd if=/dev/urandom of=$WORK_DIR/new.file count=100 bs=1024 || fail - -testname "apply bsdiff patch to new location with corrupted source and copy (bad new file)" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file $WORK_DIR/new.file $NEW_SHA1 $NEW_SIZE $OLD_SHA1:$WORK_DIR/patch.bsdiff $BAD1_SHA1:$WORK_DIR/foo && fail - -# --------------- apply patch with low space on /system ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR -$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR - -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS; we'll soon fix that." -echo run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail -run_command dd if=/dev/zero of=$WORK_DIR/bloat.dat count=$((free_kb-512)) bs=1024 || fail -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS now." - -testname "apply bsdiff patch with low space" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -testname "reapply bsdiff patch with low space" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -# --------------- apply patch with low space on /system and /cache ---------------------- - -$ADB push $DATA_DIR/old.file $WORK_DIR -$ADB push $DATA_DIR/patch.bsdiff $WORK_DIR - -free_kb=$(free_space $WORK_FS) -echo "${free_kb}kb free on /$WORK_FS" - -run_command mkdir /cache/subdir -run_command 'echo > /cache/subdir/a.file' -run_command 'echo > /cache/a.file' -run_command mkdir /cache/recovery /cache/recovery/otatest -run_command 'echo > /cache/recovery/otatest/b.file' -run_command "echo > $CACHE_TEMP_SOURCE" -free_kb=$(free_space cache) -echo "${free_kb}kb free on /cache; we'll soon fix that." -run_command dd if=/dev/zero of=/cache/bloat_small.dat count=128 bs=1024 || fail -run_command dd if=/dev/zero of=/cache/bloat_large.dat count=$((free_kb-640)) bs=1024 || fail -free_kb=$(free_space cache) -echo "${free_kb}kb free on /cache now." - -testname "apply bsdiff patch with low space, full cache, can't delete enough" -$ADB shell 'cat >> /cache/bloat_large.dat' & open_pid=$! -echo "open_pid is $open_pid" - -# size check should fail even though it deletes some stuff -run_command $WORK_DIR/applypatch -s $NEW_SIZE && fail -run_command ls /cache/bloat_small.dat && fail # was deleted -run_command ls /cache/a.file && fail # was deleted -run_command ls /cache/recovery/otatest/b.file && fail # was deleted -run_command ls /cache/bloat_large.dat || fail # wasn't deleted because it was open -run_command ls /cache/subdir/a.file || fail # wasn't deleted because it's in a subdir -run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy - -# should fail; not enough files can be deleted -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff && fail -run_command ls /cache/bloat_large.dat || fail # wasn't deleted because it was open -run_command ls /cache/subdir/a.file || fail # wasn't deleted because it's in a subdir -run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy - -kill $open_pid # /cache/bloat_large.dat is no longer open - -testname "apply bsdiff patch with low space, full cache, can delete enough" - -# should succeed after deleting /cache/bloat_large.dat -run_command $WORK_DIR/applypatch -s $NEW_SIZE || fail -run_command ls /cache/bloat_large.dat && fail # was deleted -run_command ls /cache/subdir/a.file || fail # still wasn't deleted because it's in a subdir -run_command ls $CACHE_TEMP_SOURCE || fail # wasn't deleted because it's the source file copy - -# should succeed -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail -run_command ls /cache/subdir/a.file || fail # still wasn't deleted because it's in a subdir -run_command ls $CACHE_TEMP_SOURCE && fail # was deleted because patching overwrote it, then deleted it - -# --------------- apply patch from cache ---------------------- - -$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE -# put some junk in the old file -run_command dd if=/dev/urandom of=$WORK_DIR/old.file count=100 bs=1024 || fail - -testname "apply bsdiff patch from cache (corrupted source) with low space" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - -$ADB push $DATA_DIR/old.file $CACHE_TEMP_SOURCE -# remove the old file entirely -run_command rm $WORK_DIR/old.file - -testname "apply bsdiff patch from cache (missing source) with low space" -run_command $WORK_DIR/applypatch $WORK_DIR/old.file - $NEW_SHA1 $NEW_SIZE $BAD1_SHA1:$WORK_DIR/foo $OLD_SHA1:$WORK_DIR/patch.bsdiff || fail -$ADB pull $WORK_DIR/old.file $tmpdir/patched -diff -q $DATA_DIR/new.file $tmpdir/patched || fail - - -# --------------- cleanup ---------------------- - -cleanup - -echo -echo PASS -echo - diff --git a/applypatch/applypatch_modes.cpp b/applypatch/applypatch_modes.cpp index 7b191a801..aa32d57ef 100644 --- a/applypatch/applypatch_modes.cpp +++ b/applypatch/applypatch_modes.cpp @@ -44,19 +44,6 @@ static int CheckMode(int argc, const char** argv) { return applypatch_check(argv[2], sha1); } -static int SpaceMode(int argc, const char** argv) { - if (argc != 3) { - return 2; - } - - size_t bytes; - if (!android::base::ParseUint(argv[2], &bytes) || bytes == 0) { - printf("can't parse \"%s\" as byte count\n\n", argv[2]); - return 1; - } - return CacheSizeCheck(bytes); -} - // Parse arguments (which should be of the form "<sha1>:<filename>" into the // new parallel arrays *sha1s and *files. Returns true on success. static bool ParsePatchArgs(int argc, const char** argv, std::vector<std::string>* sha1s, @@ -175,13 +162,12 @@ int applypatch_modes(int argc, const char** argv) { "usage: %s [-b <bonus-file>] <src-file> <tgt-file> <tgt-sha1> <tgt-size> " "[<src-sha1>:<patch> ...]\n" " or %s -c <file> [<sha1> ...]\n" - " or %s -s <bytes>\n" " or %s -l\n" "\n" "Filenames may be of the form\n" " EMMC:<partition>:<len_1>:<sha1_1>:<len_2>:<sha1_2>:...\n" "to specify reading from or writing to an EMMC partition.\n\n", - argv[0], argv[0], argv[0], argv[0]); + argv[0], argv[0], argv[0]); return 2; } @@ -191,8 +177,6 @@ int applypatch_modes(int argc, const char** argv) { result = ShowLicenses(); } else if (strncmp(argv[1], "-c", 3) == 0) { result = CheckMode(argc, argv); - } else if (strncmp(argv[1], "-s", 3) == 0) { - result = SpaceMode(argc, argv); } else { result = PatchMode(argc, argv); } diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp index 9920c2be1..65ee614ef 100644 --- a/applypatch/bspatch.cpp +++ b/applypatch/bspatch.cpp @@ -23,10 +23,14 @@ #include <stdio.h> #include <sys/types.h> +#include <string> + +#include <android-base/logging.h> #include <bspatch.h> +#include <openssl/sha.h> #include "applypatch/applypatch.h" -#include "openssl/sha.h" +#include "print_sha1.h" void ShowBSDiffLicense() { puts("The bsdiff library used herein is:\n" @@ -60,25 +64,31 @@ void ShowBSDiffLicense() { ); } -int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, const Value* patch, - ssize_t patch_offset, SinkFn sink, void* token, SHA_CTX* ctx) { - auto sha_sink = [&](const uint8_t* data, size_t len) { - len = sink(data, len, token); +int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch, + size_t patch_offset, SinkFn sink, SHA_CTX* ctx) { + auto sha_sink = [&sink, &ctx](const uint8_t* data, size_t len) { + len = sink(data, len); if (ctx) SHA1_Update(ctx, data, len); return len; }; - return bsdiff::bspatch(old_data, old_size, - reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]), - patch->data.size(), sha_sink); -} -int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, const Value* patch, - ssize_t patch_offset, std::vector<unsigned char>* new_data) { - auto vector_sink = [new_data](const uint8_t* data, size_t len) { - new_data->insert(new_data->end(), data, data + len); - return len; - }; - return bsdiff::bspatch(old_data, old_size, - reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]), - patch->data.size(), vector_sink); -} + CHECK(patch != nullptr); + CHECK_LE(patch_offset, patch->data.size()); + + int result = bsdiff::bspatch(old_data, old_size, + reinterpret_cast<const uint8_t*>(&patch->data[patch_offset]), + patch->data.size() - patch_offset, sha_sink); + if (result != 0) { + LOG(ERROR) << "bspatch failed, result: " << result; + // print SHA1 of the patch in the case of a data error. + if (result == 2) { + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast<const uint8_t*>(patch->data.data() + patch_offset), + patch->data.size() - patch_offset, digest); + std::string patch_sha1 = print_sha1(digest); + LOG(ERROR) << "Patch may be corrupted, offset: " << patch_offset << ", SHA1: " + << patch_sha1; + } + } + return result; +}
\ No newline at end of file diff --git a/applypatch/imgdiff_test.sh b/applypatch/imgdiff_test.sh deleted file mode 100755 index dcdb922b4..000000000 --- a/applypatch/imgdiff_test.sh +++ /dev/null @@ -1,118 +0,0 @@ -#!/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 - diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp index adcc61fd6..df75f98d4 100644 --- a/applypatch/imgpatch.cpp +++ b/applypatch/imgpatch.cpp @@ -26,12 +26,14 @@ #include <sys/stat.h> #include <unistd.h> +#include <memory> #include <string> #include <vector> +#include <android-base/logging.h> +#include <android-base/memory.h> #include <applypatch/applypatch.h> #include <applypatch/imgdiff.h> -#include <android-base/memory.h> #include <openssl/sha.h> #include <zlib.h> @@ -43,12 +45,91 @@ static inline int32_t Read4(const void *address) { return android::base::get_unaligned<int32_t>(address); } -int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, - const unsigned char* patch_data, ssize_t patch_size, - SinkFn sink, void* token) { +// This function is a wrapper of ApplyBSDiffPatch(). It has a custom sink function to deflate the +// patched data and stream the deflated data to output. +static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_len, + const Value* patch, size_t patch_offset, + const char* deflate_header, SinkFn sink, SHA_CTX* ctx) { + size_t expected_target_length = static_cast<size_t>(Read8(deflate_header + 32)); + int level = Read4(deflate_header + 40); + int method = Read4(deflate_header + 44); + int window_bits = Read4(deflate_header + 48); + int mem_level = Read4(deflate_header + 52); + int strategy = Read4(deflate_header + 56); + + std::unique_ptr<z_stream, decltype(&deflateEnd)> strm(new z_stream(), deflateEnd); + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + strm->avail_in = 0; + strm->next_in = nullptr; + int ret = deflateInit2(strm.get(), level, method, window_bits, mem_level, strategy); + if (ret != Z_OK) { + LOG(ERROR) << "Failed to init uncompressed data deflation: " << ret; + return false; + } + + // Define a custom sink wrapper that feeds to bspatch. It deflates the available patch data on + // the fly and outputs the compressed data to the given sink. + size_t actual_target_length = 0; + size_t total_written = 0; + static constexpr size_t buffer_size = 32768; + auto compression_sink = [&](const uint8_t* data, size_t len) -> size_t { + // The input patch length for an update never exceeds INT_MAX. + strm->avail_in = len; + strm->next_in = data; + do { + std::vector<uint8_t> buffer(buffer_size); + strm->avail_out = buffer_size; + strm->next_out = buffer.data(); + if (actual_target_length + len < expected_target_length) { + ret = deflate(strm.get(), Z_NO_FLUSH); + } else { + ret = deflate(strm.get(), Z_FINISH); + } + if (ret != Z_OK && ret != Z_STREAM_END) { + LOG(ERROR) << "Failed to deflate stream: " << ret; + // zero length indicates an error in the sink function of bspatch(). + return 0; + } + + size_t have = buffer_size - strm->avail_out; + total_written += have; + if (sink(buffer.data(), have) != have) { + LOG(ERROR) << "Failed to write " << have << " compressed bytes to output."; + return 0; + } + if (ctx) SHA1_Update(ctx, buffer.data(), have); + } while ((strm->avail_in != 0 || strm->avail_out == 0) && ret != Z_STREAM_END); + + actual_target_length += len; + return len; + }; + + if (ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink, nullptr) != 0) { + return false; + } + + if (ret != Z_STREAM_END) { + LOG(ERROR) << "ret is expected to be Z_STREAM_END, but it's " << ret; + return false; + } + + if (expected_target_length != actual_target_length) { + LOG(ERROR) << "target length is expected to be " << expected_target_length << ", but it's " + << actual_target_length; + return false; + } + LOG(DEBUG) << "bspatch writes " << total_written << " bytes in total to streaming output."; + + return true; +} + +int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data, + size_t patch_size, SinkFn sink) { Value patch(VAL_BLOB, std::string(reinterpret_cast<const char*>(patch_data), patch_size)); - return ApplyImagePatch(old_data, old_size, &patch, sink, token, nullptr, nullptr); + return ApplyImagePatch(old_data, old_size, &patch, sink, nullptr, nullptr); } /* @@ -57,8 +138,8 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, * file, and update the SHA context with the output data as well. * Return 0 on success. */ -int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value* patch, - SinkFn sink, void* token, SHA_CTX* ctx, const Value* bonus_data) { +int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink, + SHA_CTX* ctx, const Value* bonus_data) { if (patch->data.size() < 12) { printf("patch too short to contain header\n"); return -1; @@ -97,11 +178,14 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value size_t src_len = static_cast<size_t>(Read8(normal_header + 8)); size_t patch_offset = static_cast<size_t>(Read8(normal_header + 16)); - if (src_start + src_len > static_cast<size_t>(old_size)) { + if (src_start + src_len > old_size) { printf("source data too short\n"); return -1; } - ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, token, ctx); + if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, ctx) != 0) { + printf("Failed to apply bsdiff patch.\n"); + return -1; + } } else if (type == CHUNK_RAW) { const char* raw_header = &patch->data[pos]; pos += 4; @@ -110,15 +194,14 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value return -1; } - ssize_t data_len = Read4(raw_header); + size_t data_len = static_cast<size_t>(Read4(raw_header)); if (pos + data_len > patch->data.size()) { printf("failed to read chunk %d raw data\n", i); return -1; } if (ctx) SHA1_Update(ctx, &patch->data[pos], data_len); - if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]), data_len, token) != - data_len) { + if (sink(reinterpret_cast<const unsigned char*>(&patch->data[pos]), data_len) != data_len) { printf("failed to write chunk %d raw data\n", i); return -1; } @@ -136,14 +219,8 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value size_t src_len = static_cast<size_t>(Read8(deflate_header + 8)); size_t patch_offset = static_cast<size_t>(Read8(deflate_header + 16)); size_t expanded_len = static_cast<size_t>(Read8(deflate_header + 24)); - size_t target_len = static_cast<size_t>(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); - - if (src_start + src_len > static_cast<size_t>(old_size)) { + + if (src_start + src_len > old_size) { printf("source data too short\n"); return -1; } @@ -198,58 +275,12 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value } } - // Next, apply the bsdiff patch (in memory) to the uncompressed data. - std::vector<unsigned char> uncompressed_target_data; - // TODO(senj): Remove the only usage of ApplyBSDiffPatchMem here, - // replace it with ApplyBSDiffPatch with a custom sink function that - // wraps the given sink function to stream output to save memory. - if (ApplyBSDiffPatchMem(expanded_source.data(), expanded_len, patch, patch_offset, - &uncompressed_target_data) != 0) { - return -1; - } - if (uncompressed_target_data.size() != target_len) { - printf("expected target len to be %zu, but it's %zu\n", target_len, - uncompressed_target_data.size()); + if (!ApplyBSDiffPatchAndStreamOutput(expanded_source.data(), expanded_len, patch, + patch_offset, deflate_header, sink, ctx)) { + LOG(ERROR) << "Fail to apply streaming bspatch."; 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. - if (expanded_source.size() < 32768U) { - expanded_source.resize(32768U); - } - - { - std::vector<unsigned char>& temp_data = expanded_source; - - // now the deflate stream - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = uncompressed_target_data.size(); - strm.next_in = uncompressed_target_data.data(); - int ret = deflateInit2(&strm, level, method, windowBits, memLevel, strategy); - if (ret != Z_OK) { - printf("failed to init uncompressed data deflation: %d\n", ret); - return -1; - } - do { - strm.avail_out = temp_data.size(); - strm.next_out = temp_data.data(); - ret = deflate(&strm, Z_FINISH); - ssize_t have = temp_data.size() - strm.avail_out; - - if (sink(temp_data.data(), have, token) != have) { - printf("failed to write %zd compressed bytes to output\n", have); - return -1; - } - if (ctx) SHA1_Update(ctx, temp_data.data(), have); - } while (ret != Z_STREAM_END); - deflateEnd(&strm); - } } else { printf("patch chunk %d is unknown type %d\n", i, type); return -1; diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h index 4489decb6..581360ef1 100644 --- a/applypatch/include/applypatch/applypatch.h +++ b/applypatch/include/applypatch/applypatch.h @@ -20,6 +20,7 @@ #include <stdint.h> #include <sys/stat.h> +#include <functional> #include <memory> #include <string> #include <vector> @@ -41,7 +42,7 @@ struct FileContents { // and use it as the source instead. #define CACHE_TEMP_SOURCE "/cache/saved.file" -typedef ssize_t (*SinkFn)(const unsigned char*, ssize_t, void*); +using SinkFn = std::function<size_t(const unsigned char*, size_t)>; // applypatch.cpp int ShowLicenses(); @@ -66,18 +67,12 @@ int SaveFileContents(const char* filename, const FileContents* file); // bspatch.cpp void ShowBSDiffLicense(); -int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, - const Value* patch, ssize_t patch_offset, - SinkFn sink, void* token, SHA_CTX* ctx); -int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size, - const Value* patch, ssize_t patch_offset, - std::vector<unsigned char>* new_data); +int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value* patch, + size_t patch_offset, SinkFn sink, SHA_CTX* ctx); // imgpatch.cpp -int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, - const Value* patch, - SinkFn sink, void* token, SHA_CTX* ctx, - const Value* bonus_data); +int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value* patch, SinkFn sink, + SHA_CTX* ctx, const Value* bonus_data); // freecache.cpp int MakeFreeSpaceOnCache(size_t bytes_needed); diff --git a/applypatch/include/applypatch/imgpatch.h b/applypatch/include/applypatch/imgpatch.h index 6549f79f0..07c66094f 100644 --- a/applypatch/include/applypatch/imgpatch.h +++ b/applypatch/include/applypatch/imgpatch.h @@ -19,10 +19,11 @@ #include <sys/types.h> -using SinkFn = ssize_t (*)(const unsigned char*, ssize_t, void*); +#include <functional> -int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, - const unsigned char* patch_data, ssize_t patch_size, - SinkFn sink, void* token); +using SinkFn = std::function<size_t(const unsigned char*, size_t)>; + +int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data, + size_t patch_size, SinkFn sink); #endif // _APPLYPATCH_IMGPATCH_H diff --git a/applypatch/testdata/new.file b/applypatch/testdata/new.file Binary files differdeleted file mode 100644 index cdeb8fd50..000000000 --- a/applypatch/testdata/new.file +++ /dev/null diff --git a/applypatch/testdata/old.file b/applypatch/testdata/old.file Binary files differdeleted file mode 100644 index 166c8732e..000000000 --- a/applypatch/testdata/old.file +++ /dev/null diff --git a/applypatch/testdata/patch.bsdiff b/applypatch/testdata/patch.bsdiff Binary files differdeleted file mode 100644 index b78d38573..000000000 --- a/applypatch/testdata/patch.bsdiff +++ /dev/null |