summaryrefslogtreecommitdiffstats
path: root/applypatch
diff options
context:
space:
mode:
Diffstat (limited to 'applypatch')
-rw-r--r--applypatch/applypatch.cpp45
-rwxr-xr-xapplypatch/applypatch.sh350
-rw-r--r--applypatch/applypatch_modes.cpp18
-rw-r--r--applypatch/bspatch.cpp48
-rwxr-xr-xapplypatch/imgdiff_test.sh118
-rw-r--r--applypatch/imgpatch.cpp169
-rw-r--r--applypatch/include/applypatch/applypatch.h17
-rw-r--r--applypatch/include/applypatch/imgpatch.h9
-rw-r--r--applypatch/testdata/new.filebin1388877 -> 0 bytes
-rw-r--r--applypatch/testdata/old.filebin1348051 -> 0 bytes
-rw-r--r--applypatch/testdata/patch.bsdiffbin57476 -> 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
deleted file mode 100644
index cdeb8fd50..000000000
--- a/applypatch/testdata/new.file
+++ /dev/null
Binary files differ
diff --git a/applypatch/testdata/old.file b/applypatch/testdata/old.file
deleted file mode 100644
index 166c8732e..000000000
--- a/applypatch/testdata/old.file
+++ /dev/null
Binary files differ
diff --git a/applypatch/testdata/patch.bsdiff b/applypatch/testdata/patch.bsdiff
deleted file mode 100644
index b78d38573..000000000
--- a/applypatch/testdata/patch.bsdiff
+++ /dev/null
Binary files differ