diff options
35 files changed, 1810 insertions, 1571 deletions
diff --git a/Android.mk b/Android.mk index 03adf32bf..1b83d75ff 100644 --- a/Android.mk +++ b/Android.mk @@ -33,9 +33,11 @@ include $(BUILD_STATIC_LIBRARY) # =============================== include $(CLEAR_VARS) LOCAL_SRC_FILES := mounts.cpp -LOCAL_CLANG := true -LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror +LOCAL_CFLAGS := \ + -Wall \ + -Werror LOCAL_MODULE := libmounts +LOCAL_STATIC_LIBRARIES := libbase include $(BUILD_STATIC_LIBRARY) # librecovery (static library) @@ -176,6 +178,7 @@ include $(BUILD_STATIC_LIBRARY) include \ $(LOCAL_PATH)/applypatch/Android.mk \ + $(LOCAL_PATH)/boot_control/Android.mk \ $(LOCAL_PATH)/bootloader_message/Android.mk \ $(LOCAL_PATH)/edify/Android.mk \ $(LOCAL_PATH)/minadbd/Android.mk \ 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/bspatch.cpp b/applypatch/bspatch.cpp index 9920c2be1..f75a2c680 100644 --- a/applypatch/bspatch.cpp +++ b/applypatch/bspatch.cpp @@ -24,9 +24,9 @@ #include <sys/types.h> #include <bspatch.h> +#include <openssl/sha.h> #include "applypatch/applypatch.h" -#include "openssl/sha.h" void ShowBSDiffLicense() { puts("The bsdiff library used herein is:\n" @@ -60,10 +60,10 @@ 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; }; @@ -72,8 +72,8 @@ int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size, const Valu 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) { +int ApplyBSDiffPatchMem(const unsigned char* old_data, size_t old_size, const Value* patch, + size_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; 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..7d8b7361c 100644 --- a/applypatch/imgpatch.cpp +++ b/applypatch/imgpatch.cpp @@ -43,12 +43,11 @@ 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) { +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 +56,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 +96,11 @@ 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); + ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink, ctx); } else if (type == CHUNK_RAW) { const char* raw_header = &patch->data[pos]; pos += 4; @@ -110,15 +109,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; } @@ -143,7 +141,7 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value 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; } @@ -240,9 +238,9 @@ int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size, const Value 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; + size_t have = temp_data.size() - strm.avail_out; - if (sink(temp_data.data(), have, token) != have) { + if (sink(temp_data.data(), have) != have) { printf("failed to write %zd compressed bytes to output\n", have); return -1; } diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h index 4489decb6..da55432d5 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,14 @@ 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); +int ApplyBSDiffPatchMem(const unsigned char* old_data, size_t old_size, const Value* patch, + size_t patch_offset, std::vector<unsigned char>* new_data); // 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 diff --git a/boot_control/Android.mk b/boot_control/Android.mk new file mode 100644 index 000000000..27e3d9765 --- /dev/null +++ b/boot_control/Android.mk @@ -0,0 +1,34 @@ +# +# Copyright (C) 2017 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. +# + +LOCAL_PATH := $(my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := bootctrl.bcb +LOCAL_MODULE_RELATIVE_PATH := hw +LOCAL_SRC_FILES := boot_control.cpp +LOCAL_CFLAGS := \ + -D_FILE_OFFSET_BITS=64 \ + -Werror \ + -Wall \ + -Wextra \ + -Wno-unused-parameter +LOCAL_SHARED_LIBRARIES := liblog +LOCAL_STATIC_LIBRARIES := libbootloader_message libfs_mgr libbase +LOCAL_POST_INSTALL_CMD := \ + $(hide) mkdir -p $(TARGET_OUT_SHARED_LIBRARIES)/hw && \ + ln -sf bootctrl.bcb.so $(TARGET_OUT_SHARED_LIBRARIES)/hw/bootctrl.default.so +include $(BUILD_SHARED_LIBRARY) diff --git a/boot_control/boot_control.cpp b/boot_control/boot_control.cpp new file mode 100644 index 000000000..ec97b6ced --- /dev/null +++ b/boot_control/boot_control.cpp @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2015 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 <endian.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> + +#include <string> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <android-base/stringprintf.h> +#include <android-base/unique_fd.h> +#include <hardware/boot_control.h> +#include <hardware/hardware.h> + +#include <bootloader_message/bootloader_message.h> + +struct boot_control_private_t { + // The base struct needs to be first in the list. + boot_control_module_t base; + + // Whether this struct was initialized with data from the bootloader message + // that doesn't change until next reboot. + bool initialized; + + // The path to the misc_device as reported in the fstab. + const char* misc_device; + + // The number of slots present on the device. + unsigned int num_slots; + + // The slot where we are running from. + unsigned int current_slot; +}; + +namespace { + +// The number of boot attempts that should be made from a new slot before +// rolling back to the previous slot. +constexpr unsigned int kDefaultBootAttempts = 7; +static_assert(kDefaultBootAttempts < 8, "tries_remaining field only has 3 bits"); + +constexpr unsigned int kMaxNumSlots = + sizeof(bootloader_control::slot_info) / sizeof(bootloader_control::slot_info[0]); +constexpr const char* kSlotSuffixes[kMaxNumSlots] = { "_a", "_b", "_c", "_d" }; +constexpr off_t kBootloaderControlOffset = offsetof(bootloader_message_ab, slot_suffix); + +static uint32_t CRC32(const uint8_t* buf, size_t size) { + static uint32_t crc_table[256]; + + // Compute the CRC-32 table only once. + if (!crc_table[1]) { + for (uint32_t i = 0; i < 256; ++i) { + uint32_t crc = i; + for (uint32_t j = 0; j < 8; ++j) { + uint32_t mask = -(crc & 1); + crc = (crc >> 1) ^ (0xEDB88320 & mask); + } + crc_table[i] = crc; + } + } + + uint32_t ret = -1; + for (size_t i = 0; i < size; ++i) { + ret = (ret >> 8) ^ crc_table[(ret ^ buf[i]) & 0xFF]; + } + + return ~ret; +} + +// Return the little-endian representation of the CRC-32 of the first fields +// in |boot_ctrl| up to the crc32_le field. +uint32_t BootloaderControlLECRC(const bootloader_control* boot_ctrl) { + return htole32( + CRC32(reinterpret_cast<const uint8_t*>(boot_ctrl), offsetof(bootloader_control, crc32_le))); +} + +bool LoadBootloaderControl(const char* misc_device, bootloader_control* buffer) { + android::base::unique_fd fd(open(misc_device, O_RDONLY)); + if (fd.get() == -1) { + PLOG(ERROR) << "failed to open " << misc_device; + return false; + } + if (lseek(fd, kBootloaderControlOffset, SEEK_SET) != kBootloaderControlOffset) { + PLOG(ERROR) << "failed to lseek " << misc_device; + return false; + } + if (!android::base::ReadFully(fd.get(), buffer, sizeof(bootloader_control))) { + PLOG(ERROR) << "failed to read " << misc_device; + return false; + } + return true; +} + +bool UpdateAndSaveBootloaderControl(const char* misc_device, bootloader_control* buffer) { + buffer->crc32_le = BootloaderControlLECRC(buffer); + android::base::unique_fd fd(open(misc_device, O_WRONLY | O_SYNC)); + if (fd.get() == -1) { + PLOG(ERROR) << "failed to open " << misc_device; + return false; + } + if (lseek(fd.get(), kBootloaderControlOffset, SEEK_SET) != kBootloaderControlOffset) { + PLOG(ERROR) << "failed to lseek " << misc_device; + return false; + } + if (!android::base::WriteFully(fd.get(), buffer, sizeof(bootloader_control))) { + PLOG(ERROR) << "failed to write " << misc_device; + return false; + } + return true; +} + +void InitDefaultBootloaderControl(const boot_control_private_t* module, + bootloader_control* boot_ctrl) { + memset(boot_ctrl, 0, sizeof(*boot_ctrl)); + + if (module->current_slot < kMaxNumSlots) { + strlcpy(boot_ctrl->slot_suffix, kSlotSuffixes[module->current_slot], + sizeof(boot_ctrl->slot_suffix)); + } + boot_ctrl->magic = BOOT_CTRL_MAGIC; + boot_ctrl->version = BOOT_CTRL_VERSION; + + // Figure out the number of slots by checking if the partitions exist, + // otherwise assume the maximum supported by the header. + boot_ctrl->nb_slot = kMaxNumSlots; + std::string base_path = module->misc_device; + size_t last_path_sep = base_path.rfind('/'); + if (last_path_sep != std::string::npos) { + // We test the existence of the "boot" partition on each possible slot, + // which is a partition required by Android Bootloader Requirements. + base_path = base_path.substr(0, last_path_sep + 1) + "boot"; + int last_existing_slot = -1; + int first_missing_slot = -1; + for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) { + std::string partition_path = base_path + kSlotSuffixes[slot]; + struct stat part_stat; + int err = stat(partition_path.c_str(), &part_stat); + if (!err) { + last_existing_slot = slot; + LOG(INFO) << "Found slot: " << kSlotSuffixes[slot]; + } else if (err < 0 && errno == ENOENT && first_missing_slot == -1) { + first_missing_slot = slot; + } + } + // We only declare that we found the actual number of slots if we found all + // the boot partitions up to the number of slots, and no boot partition + // after that. Not finding any of the boot partitions implies a problem so + // we just leave the number of slots in the maximum value. + if ((last_existing_slot != -1 && last_existing_slot + 1 == first_missing_slot) || + (first_missing_slot == -1 && last_existing_slot + 1 == kMaxNumSlots)) { + boot_ctrl->nb_slot = last_existing_slot + 1; + LOG(INFO) << "Found a system with " << last_existing_slot + 1 << " slots."; + } + } + + for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) { + slot_metadata entry = {}; + + if (slot < boot_ctrl->nb_slot) { + entry.priority = 7; + entry.tries_remaining = kDefaultBootAttempts; + entry.successful_boot = 0; + } else { + entry.priority = 0; // Unbootable + } + + // When the boot_control stored on disk is invalid, we assume that the + // current slot is successful. The bootloader should repair this situation + // before booting and write a valid boot_control slot, so if we reach this + // stage it means that the misc partition was corrupted since boot. + if (module->current_slot == slot) { + entry.successful_boot = 1; + } + + boot_ctrl->slot_info[slot] = entry; + } + boot_ctrl->recovery_tries_remaining = 0; + + boot_ctrl->crc32_le = BootloaderControlLECRC(boot_ctrl); +} + +// Return the index of the slot suffix passed or -1 if not a valid slot suffix. +int SlotSuffixToIndex(const char* suffix) { + for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) { + if (!strcmp(kSlotSuffixes[slot], suffix)) return slot; + } + return -1; +} + +// Initialize the boot_control_private struct with the information from +// the bootloader_message buffer stored in |boot_ctrl|. Returns whether the +// initialization succeeded. +bool BootControl_lazyInitialization(boot_control_private_t* module) { + if (module->initialized) return true; + + // Initialize the current_slot from the read-only property. If the property + // was not set (from either the command line or the device tree), we can later + // initialize it from the bootloader_control struct. + std::string suffix_prop = android::base::GetProperty("ro.boot.slot_suffix", ""); + module->current_slot = SlotSuffixToIndex(suffix_prop.c_str()); + + std::string err; + std::string device = get_bootloader_message_blk_device(&err); + if (device.empty()) return false; + + bootloader_control boot_ctrl; + if (!LoadBootloaderControl(device.c_str(), &boot_ctrl)) return false; + + // Note that since there isn't a module unload function this memory is leaked. + module->misc_device = strdup(device.c_str()); + module->initialized = true; + + // Validate the loaded data, otherwise we will destroy it and re-initialize it + // with the current information. + uint32_t computed_crc32 = BootloaderControlLECRC(&boot_ctrl); + if (boot_ctrl.crc32_le != computed_crc32) { + LOG(WARNING) << "Invalid boot control found, expected CRC-32 0x" << std::hex << computed_crc32 + << " but found 0x" << std::hex << boot_ctrl.crc32_le << ". Re-initializing."; + InitDefaultBootloaderControl(module, &boot_ctrl); + UpdateAndSaveBootloaderControl(device.c_str(), &boot_ctrl); + } + + module->num_slots = boot_ctrl.nb_slot; + return true; +} + +void BootControl_init(boot_control_module_t* module) { + BootControl_lazyInitialization(reinterpret_cast<boot_control_private_t*>(module)); +} + +unsigned int BootControl_getNumberSlots(boot_control_module_t* module) { + return reinterpret_cast<boot_control_private_t*>(module)->num_slots; +} + +unsigned int BootControl_getCurrentSlot(boot_control_module_t* module) { + return reinterpret_cast<boot_control_private_t*>(module)->current_slot; +} + +int BootControl_markBootSuccessful(boot_control_module_t* module) { + boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module); + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + + bootctrl.slot_info[bootctrl_module->current_slot].successful_boot = 1; + // tries_remaining == 0 means that the slot is not bootable anymore, make + // sure we mark the current slot as bootable if it succeeds in the last + // attempt. + bootctrl.slot_info[bootctrl_module->current_slot].tries_remaining = 1; + if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + return 0; +} + +int BootControl_setActiveBootSlot(boot_control_module_t* module, unsigned int slot) { + boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module); + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { + // Invalid slot number. + return -1; + } + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + + // Set every other slot with a lower priority than the new "active" slot. + const unsigned int kActivePriority = 15; + const unsigned int kActiveTries = 6; + for (unsigned int i = 0; i < bootctrl_module->num_slots; ++i) { + if (i != slot) { + if (bootctrl.slot_info[i].priority >= kActivePriority) + bootctrl.slot_info[i].priority = kActivePriority - 1; + } + } + + // Note that setting a slot as active doesn't change the successful bit. + // The successful bit will only be changed by setSlotAsUnbootable(). + bootctrl.slot_info[slot].priority = kActivePriority; + bootctrl.slot_info[slot].tries_remaining = kActiveTries; + + // Setting the current slot as active is a way to revert the operation that + // set *another* slot as active at the end of an updater. This is commonly + // used to cancel the pending update. We should only reset the verity_corrpted + // bit when attempting a new slot, otherwise the verity bit on the current + // slot would be flip. + if (slot != bootctrl_module->current_slot) bootctrl.slot_info[slot].verity_corrupted = 0; + + if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + return 0; +} + +int BootControl_setSlotAsUnbootable(struct boot_control_module* module, unsigned int slot) { + boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module); + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { + // Invalid slot number. + return -1; + } + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + + // The only way to mark a slot as unbootable, regardless of the priority is to + // set the tries_remaining to 0. + bootctrl.slot_info[slot].successful_boot = 0; + bootctrl.slot_info[slot].tries_remaining = 0; + if (!UpdateAndSaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + return 0; +} + +int BootControl_isSlotBootable(struct boot_control_module* module, unsigned int slot) { + boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module); + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { + // Invalid slot number. + return -1; + } + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + + return bootctrl.slot_info[slot].tries_remaining; +} + +int BootControl_isSlotMarkedSuccessful(struct boot_control_module* module, unsigned int slot) { + boot_control_private_t* const bootctrl_module = reinterpret_cast<boot_control_private_t*>(module); + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { + // Invalid slot number. + return -1; + } + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) return -1; + + return bootctrl.slot_info[slot].successful_boot && bootctrl.slot_info[slot].tries_remaining; +} + +const char* BootControl_getSuffix(boot_control_module_t* module, unsigned int slot) { + if (slot >= kMaxNumSlots || slot >= reinterpret_cast<boot_control_private_t*>(module)->num_slots) { + return NULL; + } + return kSlotSuffixes[slot]; +} + +static int BootControl_open(const hw_module_t* module __unused, const char* id __unused, + hw_device_t** device __unused) { + /* Nothing to do currently. */ + return 0; +} + +struct hw_module_methods_t BootControl_methods = { + .open = BootControl_open, +}; + +} // namespace + +boot_control_private_t HAL_MODULE_INFO_SYM = { + .base = + { + .common = + { + .tag = HARDWARE_MODULE_TAG, + .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1, + .hal_api_version = HARDWARE_HAL_API_VERSION, + .id = BOOT_CONTROL_HARDWARE_MODULE_ID, + .name = "AOSP reference bootctrl HAL", + .author = "The Android Open Source Project", + .methods = &BootControl_methods, + }, + .init = BootControl_init, + .getNumberSlots = BootControl_getNumberSlots, + .getCurrentSlot = BootControl_getCurrentSlot, + .markBootSuccessful = BootControl_markBootSuccessful, + .setActiveBootSlot = BootControl_setActiveBootSlot, + .setSlotAsUnbootable = BootControl_setSlotAsUnbootable, + .isSlotBootable = BootControl_isSlotBootable, + .getSuffix = BootControl_getSuffix, + .isSlotMarkedSuccessful = BootControl_isSlotMarkedSuccessful, + }, + .initialized = false, + .misc_device = nullptr, + .num_slots = 0, + .current_slot = 0, +}; diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp index d8086be28..f91446b43 100644 --- a/bootloader_message/bootloader_message.cpp +++ b/bootloader_message/bootloader_message.cpp @@ -117,6 +117,13 @@ static bool write_misc_partition(const void* p, size_t size, const std::string& return true; } +std::string get_bootloader_message_blk_device(std::string* err) { + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) return ""; + if (!wait_for_device(misc_blk_device, err)) return ""; + return misc_blk_device; +} + bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device, std::string* err) { return read_misc_partition(boot, sizeof(*boot), misc_blk_device, diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h index bc5104ddf..2ffbfc9e3 100644 --- a/bootloader_message/include/bootloader_message/bootloader_message.h +++ b/bootloader_message/include/bootloader_message/bootloader_message.h @@ -180,6 +180,11 @@ static_assert(sizeof(struct bootloader_control) == #include <string> #include <vector> +// Return the block device name for the bootloader message partition and waits +// for the device for up to 10 seconds. In case of error returns the empty +// string. +std::string get_bootloader_message_blk_device(std::string* err); + // Read bootloader message into boot. Error message will be set in err. bool read_bootloader_message(bootloader_message* boot, std::string* err); diff --git a/edify/parser.yy b/edify/parser.yy index 97205fe3b..b1685eb1f 100644 --- a/edify/parser.yy +++ b/edify/parser.yy @@ -23,6 +23,8 @@ #include <string> #include <vector> +#include <android-base/macros.h> + #include "expr.h" #include "yydefs.h" #include "parser.h" @@ -121,6 +123,7 @@ arglist: /* empty */ { $$->emplace_back($1); } | arglist ',' expr { + UNUSED($1); $$->push_back(std::unique_ptr<Expr>($3)); } ; diff --git a/minadbd/Android.mk b/minadbd/Android.mk index 7eef13ee0..de0b0c890 100644 --- a/minadbd/Android.mk +++ b/minadbd/Android.mk @@ -29,6 +29,7 @@ include $(CLEAR_VARS) LOCAL_CLANG := true LOCAL_MODULE := minadbd_test +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_SRC_FILES := fuse_adb_provider_test.cpp LOCAL_CFLAGS := $(minadbd_cflags) LOCAL_C_INCLUDES := $(LOCAL_PATH) system/core/adb diff --git a/minadbd/AndroidTest.xml b/minadbd/AndroidTest.xml new file mode 100644 index 000000000..7ea235b7c --- /dev/null +++ b/minadbd/AndroidTest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> +<configuration description="Config for minadbd_test"> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="minadbd_test->/data/local/tmp/minadbd_test" /> + </target_preparer> + <option name="test-suite-tag" value="apct" /> + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="minadbd_test" /> + </test> +</configuration>
\ No newline at end of file diff --git a/mounts.cpp b/mounts.cpp index f23376b06..fbcbac014 100644 --- a/mounts.cpp +++ b/mounts.cpp @@ -27,6 +27,8 @@ #include <string> #include <vector> +#include <android-base/logging.h> + struct MountedVolume { std::string device; std::string mount_point; @@ -75,15 +77,23 @@ MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) { } int unmount_mounted_volume(MountedVolume* volume) { - // Intentionally pass the empty string to umount if the caller tries - // to unmount a volume they already unmounted using this - // function. - std::string mount_point = volume->mount_point; - volume->mount_point.clear(); - return umount(mount_point.c_str()); + // Intentionally pass the empty string to umount if the caller tries to unmount a volume they + // already unmounted using this function. + std::string mount_point = volume->mount_point; + volume->mount_point.clear(); + int result = umount(mount_point.c_str()); + if (result == -1) { + PLOG(WARNING) << "Failed to umount " << mount_point; + } + return result; } int remount_read_only(MountedVolume* volume) { - return mount(volume->device.c_str(), volume->mount_point.c_str(), volume->filesystem.c_str(), - MS_NOATIME | MS_NODEV | MS_NODIRATIME | MS_RDONLY | MS_REMOUNT, 0); + int result = mount(volume->device.c_str(), volume->mount_point.c_str(), + volume->filesystem.c_str(), + MS_NOATIME | MS_NODEV | MS_NODIRATIME | MS_RDONLY | MS_REMOUNT, 0); + if (result == -1) { + PLOG(WARNING) << "Failed to remount read-only " << volume->mount_point; + } + return result; } diff --git a/otafault/Android.mk b/otafault/Android.mk index 71c2c62f6..ec4cdb365 100644 --- a/otafault/Android.mk +++ b/otafault/Android.mk @@ -23,7 +23,12 @@ otafault_static_libs := \ libbase \ liblog -LOCAL_CFLAGS := -Werror +LOCAL_CFLAGS := \ + -Werror \ + -Wthread-safety \ + -Wthread-safety-negative \ + -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS + LOCAL_SRC_FILES := config.cpp ota_io.cpp LOCAL_MODULE_TAGS := eng LOCAL_MODULE := libotafault diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp index 3a89bb5dd..faae5275d 100644 --- a/otafault/ota_io.cpp +++ b/otafault/ota_io.cpp @@ -24,10 +24,13 @@ #include <map> #include <memory> +#include <mutex> +#include <android-base/thread_annotations.h> #include "config.h" -static std::map<intptr_t, const char*> filename_cache; +static std::mutex filename_mutex; +static std::map<intptr_t, const char*> filename_cache GUARDED_BY(filename_mutex); static std::string read_fault_file_name = ""; static std::string write_fault_file_name = ""; static std::string fsync_fault_file_name = ""; @@ -55,23 +58,28 @@ bool have_eio_error = false; int ota_open(const char* path, int oflags) { // Let the caller handle errors; we do not care if open succeeds or fails int fd = open(path, oflags); + std::lock_guard<std::mutex> lock(filename_mutex); filename_cache[fd] = path; return fd; } int ota_open(const char* path, int oflags, mode_t mode) { int fd = open(path, oflags, mode); + std::lock_guard<std::mutex> lock(filename_mutex); filename_cache[fd] = path; - return fd; } + return fd; +} FILE* ota_fopen(const char* path, const char* mode) { FILE* fh = fopen(path, mode); + std::lock_guard<std::mutex> lock(filename_mutex); filename_cache[(intptr_t)fh] = path; return fh; } static int __ota_close(int fd) { // descriptors can be reused, so make sure not to leave them in the cache + std::lock_guard<std::mutex> lock(filename_mutex); filename_cache.erase(fd); return close(fd); } @@ -85,6 +93,7 @@ int ota_close(unique_fd& fd) { } static int __ota_fclose(FILE* fh) { + std::lock_guard<std::mutex> lock(filename_mutex); filename_cache.erase(reinterpret_cast<intptr_t>(fh)); return fclose(fh); } @@ -99,6 +108,7 @@ int ota_fclose(unique_file& fh) { size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) { if (should_fault_inject(OTAIO_READ)) { + std::lock_guard<std::mutex> lock(filename_mutex); auto cached = filename_cache.find((intptr_t)stream); const char* cached_path = cached->second; if (cached != filename_cache.end() && @@ -119,6 +129,7 @@ size_t ota_fread(void* ptr, size_t size, size_t nitems, FILE* stream) { ssize_t ota_read(int fd, void* buf, size_t nbyte) { if (should_fault_inject(OTAIO_READ)) { + std::lock_guard<std::mutex> lock(filename_mutex); auto cached = filename_cache.find(fd); const char* cached_path = cached->second; if (cached != filename_cache.end() @@ -138,6 +149,7 @@ ssize_t ota_read(int fd, void* buf, size_t nbyte) { size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream) { if (should_fault_inject(OTAIO_WRITE)) { + std::lock_guard<std::mutex> lock(filename_mutex); auto cached = filename_cache.find((intptr_t)stream); const char* cached_path = cached->second; if (cached != filename_cache.end() && @@ -157,6 +169,7 @@ size_t ota_fwrite(const void* ptr, size_t size, size_t count, FILE* stream) { ssize_t ota_write(int fd, const void* buf, size_t nbyte) { if (should_fault_inject(OTAIO_WRITE)) { + std::lock_guard<std::mutex> lock(filename_mutex); auto cached = filename_cache.find(fd); const char* cached_path = cached->second; if (cached != filename_cache.end() && @@ -176,6 +189,7 @@ ssize_t ota_write(int fd, const void* buf, size_t nbyte) { int ota_fsync(int fd) { if (should_fault_inject(OTAIO_FSYNC)) { + std::lock_guard<std::mutex> lock(filename_mutex); auto cached = filename_cache.find(fd); const char* cached_path = cached->second; if (cached != filename_cache.end() && diff --git a/tests/Android.mk b/tests/Android.mk index 1e433a76e..a1f0d4892 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -20,11 +20,12 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_CFLAGS := -Werror LOCAL_MODULE := recovery_unit_test -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_STATIC_LIBRARIES := \ libverifier \ libminui \ libotautil \ + libupdater \ libziparchive \ libutils \ libz \ @@ -35,6 +36,7 @@ LOCAL_SRC_FILES := \ unit/asn1_decoder_test.cpp \ unit/dirutil_test.cpp \ unit/locale_test.cpp \ + unit/rangeset_test.cpp \ unit/sysutil_test.cpp \ unit/zip_test.cpp \ unit/ziputil_test.cpp @@ -45,10 +47,8 @@ include $(BUILD_NATIVE_TEST) # Manual tests include $(CLEAR_VARS) -LOCAL_CLANG := true LOCAL_CFLAGS := -Werror LOCAL_MODULE := recovery_manual_test -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_STATIC_LIBRARIES := \ libminui \ libbase @@ -85,8 +85,8 @@ LOCAL_CFLAGS := \ -Werror \ -D_FILE_OFFSET_BITS=64 -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_MODULE := recovery_component_test +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_C_INCLUDES := bootable/recovery LOCAL_SRC_FILES := \ component/applypatch_test.cpp \ diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml new file mode 100644 index 000000000..3999aa57d --- /dev/null +++ b/tests/AndroidTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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. +--> +<configuration description="Config for recovery_component_test and recovery_unit_test"> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="recovery_component_test->/data/local/tmp/recovery_component_test" /> + <option name="push" value="recovery_unit_test->/data/local/tmp/recovery_unit_test" /> + </target_preparer> + <option name="test-suite-tag" value="apct" /> + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="recovery_component_test" /> + </test> + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="recovery_unit_test" /> + </test> +</configuration> diff --git a/tests/common/component_test_util.h b/tests/common/component_test_util.h deleted file mode 100644 index 3fee32d62..000000000 --- a/tests/common/component_test_util.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2017 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 agree 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 _COMPONENT_TEST_UTIL_H -#define _COMPONENT_TEST_UTIL_H - -#include <string> - -#include <android-base/properties.h> -#include <fs_mgr.h> - -// Check if the /misc entry exists in the fstab. -static bool parse_misc() { - std::unique_ptr<fstab, decltype(&fs_mgr_free_fstab)> fstab(fs_mgr_read_fstab_default(), - fs_mgr_free_fstab); - if (!fstab) { - GTEST_LOG_(INFO) << "Failed to read default fstab"; - return false; - } - - fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc"); - if (record == nullptr) { - GTEST_LOG_(INFO) << "Failed to find /misc in fstab."; - return false; - } - return true; -} - -#endif //_COMPONENT_TEST_UTIL_H - diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp index 0357accfe..b38bc7134 100644 --- a/tests/component/bootloader_message_test.cpp +++ b/tests/component/bootloader_message_test.cpp @@ -21,14 +21,13 @@ #include <bootloader_message/bootloader_message.h> #include <gtest/gtest.h> -#include "common/component_test_util.h" - class BootloaderMessageTest : public ::testing::Test { protected: BootloaderMessageTest() : has_misc(true) {} virtual void SetUp() override { - has_misc = parse_misc(); + std::string err; + has_misc = !get_bootloader_message_blk_device(&err).empty(); } virtual void TearDown() override { diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp index 2f648501c..7d00a3d53 100644 --- a/tests/component/imgdiff_test.cpp +++ b/tests/component/imgdiff_test.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include <stdio.h> + #include <string> #include <vector> @@ -27,12 +29,6 @@ using android::base::get_unaligned; -static 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; -} - // Sanity check for the given imgdiff patch header. static void verify_patch_header(const std::string& patch, size_t* num_normal, size_t* num_raw, size_t* num_deflate) { @@ -79,6 +75,18 @@ static void verify_patch_header(const std::string& patch, size_t* num_normal, si if (num_deflate != nullptr) *num_deflate = deflate; } +static void verify_patched_image(const std::string& src, const std::string& patch, + const std::string& tgt) { + std::string patched; + ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), + reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), + [&patched](const unsigned char* data, size_t len) { + patched.append(reinterpret_cast<const char*>(data), len); + return len; + })); + ASSERT_EQ(tgt, patched); +} + TEST(ImgdiffTest, invalid_args) { // Insufficient inputs. ASSERT_EQ(2, imgdiff(1, (const char* []){ "imgdiff" })); @@ -124,11 +132,7 @@ TEST(ImgdiffTest, image_mode_smoke) { ASSERT_EQ(0U, num_deflate); ASSERT_EQ(1U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, zip_mode_smoke_store) { @@ -177,11 +181,7 @@ TEST(ImgdiffTest, zip_mode_smoke_store) { ASSERT_EQ(0U, num_deflate); ASSERT_EQ(1U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, zip_mode_smoke_compressed) { @@ -230,11 +230,7 @@ TEST(ImgdiffTest, zip_mode_smoke_compressed) { ASSERT_EQ(1U, num_deflate); ASSERT_EQ(2U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) { @@ -286,11 +282,7 @@ TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) { ASSERT_EQ(1U, num_deflate); ASSERT_EQ(2U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, image_mode_simple) { @@ -333,11 +325,7 @@ TEST(ImgdiffTest, image_mode_simple) { ASSERT_EQ(1U, num_deflate); ASSERT_EQ(2U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, image_mode_different_num_chunks) { @@ -413,11 +401,7 @@ TEST(ImgdiffTest, image_mode_merge_chunks) { ASSERT_EQ(1U, num_deflate); ASSERT_EQ(2U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, image_mode_spurious_magic) { @@ -454,11 +438,7 @@ TEST(ImgdiffTest, image_mode_spurious_magic) { ASSERT_EQ(0U, num_deflate); ASSERT_EQ(1U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, image_mode_short_input1) { @@ -494,11 +474,7 @@ TEST(ImgdiffTest, image_mode_short_input1) { ASSERT_EQ(0U, num_deflate); ASSERT_EQ(1U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, image_mode_short_input2) { @@ -534,11 +510,7 @@ TEST(ImgdiffTest, image_mode_short_input2) { ASSERT_EQ(0U, num_deflate); ASSERT_EQ(1U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } TEST(ImgdiffTest, image_mode_single_entry_long) { @@ -577,9 +549,5 @@ TEST(ImgdiffTest, image_mode_single_entry_long) { ASSERT_EQ(0U, num_deflate); ASSERT_EQ(0U, num_raw); - std::string patched; - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(), - reinterpret_cast<const unsigned char*>(patch.data()), patch.size(), - MemorySink, &patched)); - ASSERT_EQ(tgt, patched); + verify_patched_image(src, patch, tgt); } diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp index 4f2b8164f..3925236a5 100644 --- a/tests/component/uncrypt_test.cpp +++ b/tests/component/uncrypt_test.cpp @@ -25,11 +25,12 @@ #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/properties.h> +#include <android-base/test_utils.h> #include <android-base/unique_fd.h> #include <bootloader_message/bootloader_message.h> #include <gtest/gtest.h> -#include "common/component_test_util.h" +using namespace std::string_literals; static const std::string UNCRYPT_SOCKET = "/dev/socket/uncrypt"; static const std::string INIT_SVC_SETUP_BCB = "init.svc.setup-bcb"; @@ -62,131 +63,108 @@ class UncryptTest : public ::testing::Test { ASSERT_TRUE(success) << "uncrypt service is not available."; - has_misc = parse_misc(); + std::string err; + has_misc = !get_bootloader_message_blk_device(&err).empty(); } - bool has_misc; -}; - -TEST_F(UncryptTest, setup_bcb) { - if (!has_misc) { - GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; - return; - } - - // Trigger the setup-bcb service. - ASSERT_TRUE(android::base::SetProperty("ctl.start", "setup-bcb")); - - // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected"). - sleep(1); - - struct sockaddr_un un = {}; - un.sun_family = AF_UNIX; - strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path)); - - int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); - ASSERT_NE(-1, sockfd); - - // Connect to the uncrypt socket. - bool success = false; - for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) { - if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) { - success = true; - break; + void SetupOrClearBcb(bool isSetup, const std::string& message, + const std::string& message_in_bcb) const { + if (!has_misc) { + GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; + return; } - sleep(1); - } - ASSERT_TRUE(success); - - // Send out the BCB message. - std::string message = "--update_message=abc value"; - std::string message_in_bcb = "recovery\n--update_message=abc value\n"; - int length = static_cast<int>(message.size()); - int length_out = htonl(length); - ASSERT_TRUE(android::base::WriteFully(sockfd, &length_out, sizeof(int))) - << "Failed to write length: " << strerror(errno); - ASSERT_TRUE(android::base::WriteFully(sockfd, message.data(), length)) - << "Failed to write message: " << strerror(errno); - // Check the status code from uncrypt. - int status; - ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int))); - ASSERT_EQ(100U, ntohl(status)); + // Trigger the setup-bcb service. + ASSERT_TRUE(android::base::SetProperty("ctl.start", isSetup ? "setup-bcb" : "clear-bcb")); - // Ack having received the status code. - int code = 0; - ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int))); - - ASSERT_EQ(0, close(sockfd)); + // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected"). + sleep(1); - ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb")); + sockaddr_un un = {}; + un.sun_family = AF_UNIX; + strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path)); - // Verify the message by reading from BCB directly. - bootloader_message boot; - std::string err; - ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err; + int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + ASSERT_NE(-1, sockfd); - ASSERT_EQ("boot-recovery", std::string(boot.command)); - ASSERT_EQ(message_in_bcb, std::string(boot.recovery)); + // Connect to the uncrypt socket. + bool success = false; + for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) { + if (connect(sockfd, reinterpret_cast<sockaddr*>(&un), sizeof(sockaddr_un)) != 0) { + success = true; + break; + } + sleep(1); + } + ASSERT_TRUE(success); + + if (isSetup) { + // Send out the BCB message. + int length = static_cast<int>(message.size()); + int length_out = htonl(length); + ASSERT_TRUE(android::base::WriteFully(sockfd, &length_out, sizeof(int))) + << "Failed to write length: " << strerror(errno); + ASSERT_TRUE(android::base::WriteFully(sockfd, message.data(), length)) + << "Failed to write message: " << strerror(errno); + } - // The rest of the boot.recovery message should be zero'd out. - ASSERT_LE(message_in_bcb.size(), sizeof(boot.recovery)); - size_t left = sizeof(boot.recovery) - message_in_bcb.size(); - ASSERT_EQ(std::string(left, '\0'), std::string(&boot.recovery[message_in_bcb.size()], left)); + // Check the status code from uncrypt. + int status; + ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int))); + ASSERT_EQ(100U, ntohl(status)); - // Clear the BCB. - ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err; -} + // Ack having received the status code. + int code = 0; + ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int))); -TEST_F(UncryptTest, clear_bcb) { - if (!has_misc) { - GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device."; - return; - } + ASSERT_EQ(0, close(sockfd)); - // Trigger the clear-bcb service. - ASSERT_TRUE(android::base::SetProperty("ctl.start", "clear-bcb")); + ASSERT_TRUE(android::base::SetProperty("ctl.stop", isSetup ? "setup-bcb" : "clear-bcb")); - // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected"). - sleep(1); + // Verify the message by reading from BCB directly. + bootloader_message boot; + std::string err; + ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err; - struct sockaddr_un un = {}; - un.sun_family = AF_UNIX; - strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path)); + if (isSetup) { + ASSERT_EQ("boot-recovery", std::string(boot.command)); + ASSERT_EQ(message_in_bcb, std::string(boot.recovery)); - int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); - ASSERT_NE(-1, sockfd); + // The rest of the boot.recovery message should be zero'd out. + ASSERT_LE(message_in_bcb.size(), sizeof(boot.recovery)); + size_t left = sizeof(boot.recovery) - message_in_bcb.size(); + ASSERT_EQ(std::string(left, '\0'), std::string(&boot.recovery[message_in_bcb.size()], left)); - // Connect to the uncrypt socket. - bool success = false; - for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) { - if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) { - success = true; - break; + // Clear the BCB. + ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err; + } else { + // All the bytes should be cleared. + ASSERT_EQ(std::string(sizeof(boot), '\0'), + std::string(reinterpret_cast<const char*>(&boot), sizeof(boot))); } - sleep(1); } - ASSERT_TRUE(success); - // Check the status code from uncrypt. - int status; - ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int))); - ASSERT_EQ(100U, ntohl(status)); - - // Ack having received the status code. - int code = 0; - ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int))); + bool has_misc; +}; - ASSERT_EQ(0, close(sockfd)); +TEST_F(UncryptTest, setup_bcb) { + std::string message = "--update_message=abc value"; + std::string message_in_bcb = "recovery\n--update_message=abc value\n"; + SetupOrClearBcb(true, message, message_in_bcb); +} - ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb")); +TEST_F(UncryptTest, clear_bcb) { + SetupOrClearBcb(false, "", ""); +} - // Verify the content by reading from BCB directly. - bootloader_message boot; - std::string err; - ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err; +TEST_F(UncryptTest, setup_bcb_wipe_ab) { + TemporaryFile wipe_package; + ASSERT_TRUE(android::base::WriteStringToFile(std::string(345, 'a'), wipe_package.path)); - // All the bytes should be cleared. - ASSERT_EQ(std::string(sizeof(boot), '\0'), - std::string(reinterpret_cast<const char*>(&boot), sizeof(boot))); + // It's expected to store a wipe package in /misc, with the package size passed to recovery. + std::string message = + "--wipe_ab\n--wipe_package="s + wipe_package.path + "\n--reason=wipePackage"s; + std::string message_in_bcb = + "recovery\n--wipe_ab\n--wipe_package_size=345\n--reason=wipePackage\n"; + SetupOrClearBcb(true, message, message_in_bcb); } diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp index 5652ddf46..dc4b5d724 100644 --- a/tests/component/updater_test.cpp +++ b/tests/component/updater_test.cpp @@ -607,3 +607,69 @@ TEST_F(UpdaterTest, block_image_update) { ASSERT_EQ(0, fclose(updater_info.cmd_pipe)); CloseArchive(handle); } + +TEST_F(UpdaterTest, new_data_short_write) { + // Create a zip file with new_data. + TemporaryFile zip_file; + FILE* zip_file_ptr = fdopen(zip_file.fd, "wb"); + ZipWriter zip_writer(zip_file_ptr); + + // Add the empty new data. + ASSERT_EQ(0, zip_writer.StartEntry("empty_new_data", 0)); + ASSERT_EQ(0, zip_writer.FinishEntry()); + // Add the short written new data. + ASSERT_EQ(0, zip_writer.StartEntry("short_new_data", 0)); + std::string new_data_short = std::string(10, 'a'); + ASSERT_EQ(0, zip_writer.WriteBytes(new_data_short.data(), new_data_short.size())); + ASSERT_EQ(0, zip_writer.FinishEntry()); + // Add the data of exactly one block. + ASSERT_EQ(0, zip_writer.StartEntry("exact_new_data", 0)); + std::string new_data_exact = std::string(4096, 'a'); + ASSERT_EQ(0, zip_writer.WriteBytes(new_data_exact.data(), new_data_exact.size())); + ASSERT_EQ(0, zip_writer.FinishEntry()); + // Add a dummy patch data. + ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0)); + ASSERT_EQ(0, zip_writer.FinishEntry()); + + std::vector<std::string> transfer_list = { + "4", + "1", + "0", + "0", + "new 2,0,1", + }; + ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0)); + std::string commands = android::base::Join(transfer_list, '\n'); + ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size())); + ASSERT_EQ(0, zip_writer.FinishEntry()); + ASSERT_EQ(0, zip_writer.Finish()); + ASSERT_EQ(0, fclose(zip_file_ptr)); + + MemMapping map; + ASSERT_EQ(0, sysMapFile(zip_file.path, &map)); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); + + // Set up the handler, command_pipe, patch offset & length. + UpdaterInfo updater_info; + updater_info.package_zip = handle; + TemporaryFile temp_pipe; + updater_info.cmd_pipe = fopen(temp_pipe.path, "wb"); + updater_info.package_zip_addr = map.addr; + updater_info.package_zip_len = map.length; + + // Updater should report the failure gracefully rather than stuck in deadlock. + TemporaryFile update_file; + std::string script_empty_data = "block_image_update(\"" + std::string(update_file.path) + + R"(", package_extract_file("transfer_list"), "empty_new_data", "patch_data"))"; + expect("", script_empty_data.c_str(), kNoCause, &updater_info); + + std::string script_short_data = "block_image_update(\"" + std::string(update_file.path) + + R"(", package_extract_file("transfer_list"), "short_new_data", "patch_data"))"; + expect("", script_short_data.c_str(), kNoCause, &updater_info); + + // Expect to write 1 block of new data successfully. + std::string script_exact_data = "block_image_update(\"" + std::string(update_file.path) + + R"(", package_extract_file("transfer_list"), "exact_new_data", "patch_data"))"; + expect("t", script_exact_data.c_str(), kNoCause, &updater_info); +} diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp index 4c0648714..61ceadc9c 100644 --- a/tests/component/verifier_test.cpp +++ b/tests/component/verifier_test.cpp @@ -132,6 +132,51 @@ TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) { package.size(), certs)); } +TEST(VerifierTest, BadPackage_AlteredFooter) { + std::string testkey_v3; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3)); + TemporaryFile key_file1; + ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file1.path)); + std::vector<Certificate> certs; + ASSERT_TRUE(load_keys(key_file1.path, certs)); + + std::string package; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package)); + ASSERT_EQ(std::string("\xc0\x06\xff\xff\xd2\x06", 6), package.substr(package.size() - 6, 6)); + + // Alter the footer. + package[package.size() - 5] = '\x05'; + ASSERT_EQ(VERIFY_FAILURE, + verify_file(reinterpret_cast<const unsigned char*>(package.data()), package.size(), + certs)); +} + +TEST(VerifierTest, BadPackage_AlteredContent) { + std::string testkey_v3; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3)); + TemporaryFile key_file1; + ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file1.path)); + std::vector<Certificate> certs; + ASSERT_TRUE(load_keys(key_file1.path, certs)); + + std::string package; + ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package)); + ASSERT_GT(package.size(), static_cast<size_t>(100)); + + // Alter the content. + std::string altered1(package); + altered1[50] += 1; + ASSERT_EQ(VERIFY_FAILURE, + verify_file(reinterpret_cast<const unsigned char*>(altered1.data()), altered1.size(), + certs)); + + std::string altered2(package); + altered2[10] += 1; + ASSERT_EQ(VERIFY_FAILURE, + verify_file(reinterpret_cast<const unsigned char*>(altered2.data()), altered2.size(), + certs)); +} + TEST_P(VerifierSuccessTest, VerifySucceed) { ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_SUCCESS); } @@ -174,6 +219,4 @@ INSTANTIATE_TEST_CASE_P(WrongHash, VerifierFailureTest, INSTANTIATE_TEST_CASE_P(BadPackage, VerifierFailureTest, ::testing::Values( std::vector<std::string>({"random.zip", "v1"}), - std::vector<std::string>({"fake-eocd.zip", "v1"}), - std::vector<std::string>({"alter-metadata.zip", "v1"}), - std::vector<std::string>({"alter-footer.zip", "v1"}))); + std::vector<std::string>({"fake-eocd.zip", "v1"}))); diff --git a/tests/testdata/alter-footer.zip b/tests/testdata/alter-footer.zip Binary files differdeleted file mode 100644 index f497ec000..000000000 --- a/tests/testdata/alter-footer.zip +++ /dev/null diff --git a/tests/testdata/alter-metadata.zip b/tests/testdata/alter-metadata.zip Binary files differdeleted file mode 100644 index 1c71fbc49..000000000 --- a/tests/testdata/alter-metadata.zip +++ /dev/null diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp new file mode 100644 index 000000000..3c6d77ef5 --- /dev/null +++ b/tests/unit/rangeset_test.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2017 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 <signal.h> +#include <sys/types.h> + +#include <vector> + +#include <gtest/gtest.h> + +#include "updater/rangeset.h" + +TEST(RangeSetTest, Parse_smoke) { + RangeSet rs = RangeSet::Parse("2,1,10"); + ASSERT_EQ(static_cast<size_t>(1), rs.size()); + ASSERT_EQ((Range{ 1, 10 }), rs[0]); + ASSERT_EQ(static_cast<size_t>(9), rs.blocks()); + + RangeSet rs2 = RangeSet::Parse("4,15,20,1,10"); + ASSERT_EQ(static_cast<size_t>(2), rs2.size()); + ASSERT_EQ((Range{ 15, 20 }), rs2[0]); + ASSERT_EQ((Range{ 1, 10 }), rs2[1]); + ASSERT_EQ(static_cast<size_t>(14), rs2.blocks()); + + // Leading zeros are fine. But android::base::ParseUint() doesn't like trailing zeros like "10 ". + ASSERT_EQ(rs, RangeSet::Parse(" 2, 1, 10")); + ASSERT_EXIT(RangeSet::Parse("2,1,10 "), ::testing::KilledBySignal(SIGABRT), ""); +} + +TEST(RangeSetTest, Parse_InvalidCases) { + // Insufficient number of tokens. + ASSERT_EXIT(RangeSet::Parse(""), ::testing::KilledBySignal(SIGABRT), ""); + ASSERT_EXIT(RangeSet::Parse("2,1"), ::testing::KilledBySignal(SIGABRT), ""); + + // The first token (i.e. the number of following tokens) is invalid. + ASSERT_EXIT(RangeSet::Parse("a,1,1"), ::testing::KilledBySignal(SIGABRT), ""); + ASSERT_EXIT(RangeSet::Parse("3,1,1"), ::testing::KilledBySignal(SIGABRT), ""); + ASSERT_EXIT(RangeSet::Parse("-3,1,1"), ::testing::KilledBySignal(SIGABRT), ""); + ASSERT_EXIT(RangeSet::Parse("2,1,2,3"), ::testing::KilledBySignal(SIGABRT), ""); + + // Invalid tokens. + ASSERT_EXIT(RangeSet::Parse("2,1,10a"), ::testing::KilledBySignal(SIGABRT), ""); + ASSERT_EXIT(RangeSet::Parse("2,,10"), ::testing::KilledBySignal(SIGABRT), ""); + + // Empty or negative range. + ASSERT_EXIT(RangeSet::Parse("2,2,2"), ::testing::KilledBySignal(SIGABRT), ""); + ASSERT_EXIT(RangeSet::Parse("2,2,1"), ::testing::KilledBySignal(SIGABRT), ""); +} + +TEST(RangeSetTest, Overlaps) { + RangeSet r1 = RangeSet::Parse("2,1,6"); + RangeSet r2 = RangeSet::Parse("2,5,10"); + ASSERT_TRUE(r1.Overlaps(r2)); + ASSERT_TRUE(r2.Overlaps(r1)); + + r2 = RangeSet::Parse("2,6,10"); + ASSERT_FALSE(r1.Overlaps(r2)); + ASSERT_FALSE(r2.Overlaps(r1)); + + ASSERT_FALSE(RangeSet::Parse("2,3,5").Overlaps(RangeSet::Parse("2,5,7"))); + ASSERT_FALSE(RangeSet::Parse("2,5,7").Overlaps(RangeSet::Parse("2,3,5"))); +} + +TEST(RangeSetTest, GetBlockNumber) { + RangeSet rs = RangeSet::Parse("2,1,10"); + ASSERT_EQ(static_cast<size_t>(1), rs.GetBlockNumber(0)); + ASSERT_EQ(static_cast<size_t>(6), rs.GetBlockNumber(5)); + ASSERT_EQ(static_cast<size_t>(9), rs.GetBlockNumber(8)); + + // Out of bound. + ASSERT_EXIT(rs.GetBlockNumber(9), ::testing::KilledBySignal(SIGABRT), ""); +} + +TEST(RangeSetTest, equality) { + ASSERT_EQ(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,1,6")); + + ASSERT_NE(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,1,7")); + ASSERT_NE(RangeSet::Parse("2,1,6"), RangeSet::Parse("2,2,7")); + + // The orders of Range's matter. "4,1,5,8,10" != "4,8,10,1,5". + ASSERT_NE(RangeSet::Parse("4,1,5,8,10"), RangeSet::Parse("4,8,10,1,5")); +} + +TEST(RangeSetTest, iterators) { + RangeSet rs = RangeSet::Parse("4,1,5,8,10"); + std::vector<Range> ranges; + for (const auto& range : rs) { + ranges.push_back(range); + } + ASSERT_EQ((std::vector<Range>{ Range{ 1, 5 }, Range{ 8, 10 } }), ranges); + + ranges.clear(); + + // Reverse iterators. + for (auto it = rs.crbegin(); it != rs.crend(); it++) { + ranges.push_back(*it); + } + ASSERT_EQ((std::vector<Range>{ Range{ 8, 10 }, Range{ 1, 5 } }), ranges); +} diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index c614ccc47..d5c170473 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -18,6 +18,7 @@ #include <errno.h> #include <dirent.h> #include <fcntl.h> +#include <inttypes.h> #include <linux/fs.h> #include <pthread.h> #include <stdarg.h> @@ -49,9 +50,10 @@ #include "edify/expr.h" #include "error_code.h" -#include "updater/install.h" #include "ota_io.h" #include "print_sha1.h" +#include "updater/install.h" +#include "updater/rangeset.h" #include "updater/updater.h" // Set this to 0 to interpret 'erase' transfers to mean do a @@ -64,100 +66,10 @@ static constexpr const char* STASH_DIRECTORY_BASE = "/cache/recovery"; static constexpr mode_t STASH_DIRECTORY_MODE = 0700; static constexpr mode_t STASH_FILE_MODE = 0600; -struct RangeSet { - size_t count; // Limit is INT_MAX. - size_t size; - std::vector<size_t> pos; // Actual limit is INT_MAX. - - // Get the block number for the ith(starting from 0) block in the range set. - int get_block(size_t idx) const { - if (idx >= size) { - LOG(ERROR) << "index: " << idx << " is greater than range set size: " << size; - return -1; - } - for (size_t i = 0; i < pos.size(); i += 2) { - if (idx < pos[i + 1] - pos[i]) { - return pos[i] + idx; - } - idx -= (pos[i + 1] - pos[i]); - } - return -1; - } -}; - static CauseCode failure_type = kNoCause; static bool is_retry = false; static std::unordered_map<std::string, RangeSet> stash_map; -static RangeSet parse_range(const std::string& range_text) { - RangeSet rs; - - std::vector<std::string> pieces = android::base::Split(range_text, ","); - if (pieces.size() < 3) { - goto err; - } - - size_t num; - if (!android::base::ParseUint(pieces[0], &num, static_cast<size_t>(INT_MAX))) { - goto err; - } - - if (num == 0 || num % 2) { - goto err; // must be even - } else if (num != pieces.size() - 1) { - goto err; - } - - rs.pos.resize(num); - rs.count = num / 2; - rs.size = 0; - - for (size_t i = 0; i < num; i += 2) { - if (!android::base::ParseUint(pieces[i + 1], &rs.pos[i], static_cast<size_t>(INT_MAX))) { - goto err; - } - - if (!android::base::ParseUint(pieces[i + 2], &rs.pos[i + 1], static_cast<size_t>(INT_MAX))) { - goto err; - } - - if (rs.pos[i] >= rs.pos[i + 1]) { - goto err; // empty or negative range - } - - size_t sz = rs.pos[i + 1] - rs.pos[i]; - if (rs.size > SIZE_MAX - sz) { - goto err; // overflow - } - - rs.size += sz; - } - - return rs; - -err: - LOG(ERROR) << "failed to parse range '" << range_text << "'"; - exit(EXIT_FAILURE); -} - -static bool range_overlaps(const RangeSet& r1, const RangeSet& r2) { - for (size_t i = 0; i < r1.count; ++i) { - size_t r1_0 = r1.pos[i * 2]; - size_t r1_1 = r1.pos[i * 2 + 1]; - - for (size_t j = 0; j < r2.count; ++j) { - size_t r2_0 = r2.pos[j * 2]; - size_t r2_1 = r2.pos[j * 2 + 1]; - - if (!(r2_0 >= r1_1 || r1_0 >= r2_1)) { - return true; - } - } - } - - return false; -} - static int read_all(int fd, uint8_t* data, size_t size) { size_t so_far = 0; while (so_far < size) { @@ -200,18 +112,17 @@ static int write_all(int fd, const std::vector<uint8_t>& buffer, size_t size) { } static bool discard_blocks(int fd, off64_t offset, uint64_t size) { - // Don't discard blocks unless the update is a retry run. - if (!is_retry) { - return true; - } - - uint64_t args[2] = {static_cast<uint64_t>(offset), size}; - int status = ioctl(fd, BLKDISCARD, &args); - if (status == -1) { - PLOG(ERROR) << "BLKDISCARD ioctl failed"; - return false; - } + // Don't discard blocks unless the update is a retry run. + if (!is_retry) { return true; + } + + uint64_t args[2] = { static_cast<uint64_t>(offset), size }; + if (ioctl(fd, BLKDISCARD, &args) == -1) { + PLOG(ERROR) << "BLKDISCARD ioctl failed"; + return false; + } + return true; } static bool check_lseek(int fd, off64_t offset, int whence) { @@ -231,180 +142,197 @@ static void allocate(size_t size, std::vector<uint8_t>& buffer) { buffer.resize(size); } -struct RangeSinkState { - explicit RangeSinkState(RangeSet& rs) : tgt(rs) { }; - - int fd; - const RangeSet& tgt; - size_t p_block; - size_t p_remain; -}; - -static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) { - RangeSinkState* rss = reinterpret_cast<RangeSinkState*>(token); +/** + * RangeSinkWriter reads data from the given FD, and writes them to the destination specified by the + * given RangeSet. + */ +class RangeSinkWriter { + public: + RangeSinkWriter(int fd, const RangeSet& tgt) + : fd_(fd), tgt_(tgt), next_range_(0), current_range_left_(0), bytes_written_(0) { + CHECK_NE(tgt.size(), static_cast<size_t>(0)); + }; + + bool Finished() const { + return next_range_ == tgt_.size() && current_range_left_ == 0; + } - if (rss->p_remain == 0) { - LOG(ERROR) << "range sink write overrun"; - return 0; + size_t Write(const uint8_t* data, size_t size) { + if (Finished()) { + LOG(ERROR) << "range sink write overrun; can't write " << size << " bytes"; + return 0; } - ssize_t written = 0; + size_t written = 0; while (size > 0) { - size_t write_now = size; - - if (rss->p_remain < write_now) { - write_now = rss->p_remain; - } + // Move to the next range as needed. + if (current_range_left_ == 0) { + if (next_range_ < tgt_.size()) { + const Range& range = tgt_[next_range_]; + off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE; + current_range_left_ = (range.second - range.first) * BLOCKSIZE; + next_range_++; + if (!discard_blocks(fd_, offset, current_range_left_)) { + break; + } - if (write_all(rss->fd, data, write_now) == -1) { + if (!check_lseek(fd_, offset, SEEK_SET)) { break; + } + } else { + // We can't write any more; return how many bytes have been written so far. + break; } + } - data += write_now; - size -= write_now; - - rss->p_remain -= write_now; - written += write_now; - - if (rss->p_remain == 0) { - // move to the next block - ++rss->p_block; - if (rss->p_block < rss->tgt.count) { - rss->p_remain = (rss->tgt.pos[rss->p_block * 2 + 1] - - rss->tgt.pos[rss->p_block * 2]) * BLOCKSIZE; - - off64_t offset = static_cast<off64_t>(rss->tgt.pos[rss->p_block*2]) * BLOCKSIZE; - if (!discard_blocks(rss->fd, offset, rss->p_remain)) { - break; - } - - if (!check_lseek(rss->fd, offset, SEEK_SET)) { - break; - } - - } else { - // we can't write any more; return how many bytes have - // been written so far. - break; - } - } + size_t write_now = size; + if (current_range_left_ < write_now) { + write_now = current_range_left_; + } + + if (write_all(fd_, data, write_now) == -1) { + break; + } + + data += write_now; + size -= write_now; + + current_range_left_ -= write_now; + written += write_now; } + bytes_written_ += written; return written; -} + } -// All of the data for all the 'new' transfers is contained in one -// file in the update package, concatenated together in the order in -// which transfers.list will need it. We want to stream it out of the -// archive (it's compressed) without writing it to a temp file, but we -// can't write each section until it's that transfer's turn to go. -// -// To achieve this, we expand the new data from the archive in a -// background thread, and block that threads 'receive uncompressed -// data' function until the main thread has reached a point where we -// want some new data to be written. We signal the background thread -// with the destination for the data and block the main thread, -// waiting for the background thread to complete writing that section. -// Then it signals the main thread to wake up and goes back to -// blocking waiting for a transfer. -// -// NewThreadInfo is the struct used to pass information back and forth -// between the two threads. When the main thread wants some data -// written, it sets rss to the destination location and signals the -// condition. When the background thread is done writing, it clears -// rss and signals the condition again. + size_t BytesWritten() const { + return bytes_written_; + } + private: + // The input data. + int fd_; + // The destination for the data. + const RangeSet& tgt_; + // The next range that we should write to. + size_t next_range_; + // The number of bytes to write before moving to the next range. + size_t current_range_left_; + // Total bytes written by the writer. + size_t bytes_written_; +}; + +/** + * All of the data for all the 'new' transfers is contained in one file in the update package, + * concatenated together in the order in which transfers.list will need it. We want to stream it out + * of the archive (it's compressed) without writing it to a temp file, but we can't write each + * section until it's that transfer's turn to go. + * + * To achieve this, we expand the new data from the archive in a background thread, and block that + * threads 'receive uncompressed data' function until the main thread has reached a point where we + * want some new data to be written. We signal the background thread with the destination for the + * data and block the main thread, waiting for the background thread to complete writing that + * section. Then it signals the main thread to wake up and goes back to blocking waiting for a + * transfer. + * + * NewThreadInfo is the struct used to pass information back and forth between the two threads. When + * the main thread wants some data written, it sets writer to the destination location and signals + * the condition. When the background thread is done writing, it clears writer and signals the + * condition again. + */ struct NewThreadInfo { - ZipArchiveHandle za; - ZipEntry entry; + ZipArchiveHandle za; + ZipEntry entry; - RangeSinkState* rss; + RangeSinkWriter* writer; + bool receiver_available; - pthread_mutex_t mu; - pthread_cond_t cv; + pthread_mutex_t mu; + pthread_cond_t cv; }; static bool receive_new_data(const uint8_t* data, size_t size, void* cookie) { - NewThreadInfo* nti = reinterpret_cast<NewThreadInfo*>(cookie); + NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie); - while (size > 0) { - // Wait for nti->rss to be non-null, indicating some of this - // data is wanted. - pthread_mutex_lock(&nti->mu); - while (nti->rss == nullptr) { - pthread_cond_wait(&nti->cv, &nti->mu); - } - pthread_mutex_unlock(&nti->mu); + while (size > 0) { + // Wait for nti->writer to be non-null, indicating some of this data is wanted. + pthread_mutex_lock(&nti->mu); + while (nti->writer == nullptr) { + pthread_cond_wait(&nti->cv, &nti->mu); + } + pthread_mutex_unlock(&nti->mu); - // At this point nti->rss is set, and we own it. The main - // thread is waiting for it to disappear from nti. - ssize_t written = RangeSinkWrite(data, size, nti->rss); - data += written; - size -= written; + // At this point nti->writer is set, and we own it. The main thread is waiting for it to + // disappear from nti. + size_t written = nti->writer->Write(data, size); + data += written; + size -= written; - if (nti->rss->p_block == nti->rss->tgt.count) { - // we have written all the bytes desired by this rss. + if (nti->writer->Finished()) { + // We have written all the bytes desired by this writer. - pthread_mutex_lock(&nti->mu); - nti->rss = nullptr; - pthread_cond_broadcast(&nti->cv); - pthread_mutex_unlock(&nti->mu); - } + pthread_mutex_lock(&nti->mu); + nti->writer = nullptr; + pthread_cond_broadcast(&nti->cv); + pthread_mutex_unlock(&nti->mu); } + } - return true; + return true; } static void* unzip_new_data(void* cookie) { - NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie); - ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti); - return nullptr; + NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie); + ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti); + + pthread_mutex_lock(&nti->mu); + nti->receiver_available = false; + if (nti->writer != nullptr) { + pthread_cond_broadcast(&nti->cv); + } + pthread_mutex_unlock(&nti->mu); + return nullptr; } static int ReadBlocks(const RangeSet& src, std::vector<uint8_t>& buffer, int fd) { - size_t p = 0; - uint8_t* data = buffer.data(); - - for (size_t i = 0; i < src.count; ++i) { - if (!check_lseek(fd, (off64_t) src.pos[i * 2] * BLOCKSIZE, SEEK_SET)) { - return -1; - } - - size_t size = (src.pos[i * 2 + 1] - src.pos[i * 2]) * BLOCKSIZE; - - if (read_all(fd, data + p, size) == -1) { - return -1; - } + size_t p = 0; + for (const auto& range : src) { + if (!check_lseek(fd, static_cast<off64_t>(range.first) * BLOCKSIZE, SEEK_SET)) { + return -1; + } - p += size; + size_t size = (range.second - range.first) * BLOCKSIZE; + if (read_all(fd, buffer.data() + p, size) == -1) { + return -1; } - return 0; + p += size; + } + + return 0; } static int WriteBlocks(const RangeSet& tgt, const std::vector<uint8_t>& buffer, int fd) { - const uint8_t* data = buffer.data(); - - size_t p = 0; - for (size_t i = 0; i < tgt.count; ++i) { - off64_t offset = static_cast<off64_t>(tgt.pos[i * 2]) * BLOCKSIZE; - size_t size = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * BLOCKSIZE; - if (!discard_blocks(fd, offset, size)) { - return -1; - } - - if (!check_lseek(fd, offset, SEEK_SET)) { - return -1; - } + size_t written = 0; + for (const auto& range : tgt) { + off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE; + size_t size = (range.second - range.first) * BLOCKSIZE; + if (!discard_blocks(fd, offset, size)) { + return -1; + } - if (write_all(fd, data + p, size) == -1) { - return -1; - } + if (!check_lseek(fd, offset, SEEK_SET)) { + return -1; + } - p += size; + if (write_all(fd, buffer.data() + written, size) == -1) { + return -1; } - return 0; + written += size; + } + + return 0; } // Parameters for transfer list command functions @@ -463,31 +391,26 @@ static void PrintHashForCorruptedSourceBlocks(const CommandParameters& params, return; } - RangeSet src = parse_range(params.tokens[pos++]); + RangeSet src = RangeSet::Parse(params.tokens[pos++]); RangeSet locs; // If there's no stashed blocks, content in the buffer is consecutive and has the same // order as the source blocks. if (pos == params.tokens.size()) { - locs.count = 1; - locs.size = src.size; - locs.pos = { 0, src.size }; + locs = RangeSet(std::vector<Range>{ Range{ 0, src.blocks() } }); } else { // Otherwise, the next token is the offset of the source blocks in the target range. // Example: for the tokens <4,63946,63947,63948,63979> <4,6,7,8,39> <stashed_blocks>; // We want to print SHA-1 for the data in buffer[6], buffer[8], buffer[9] ... buffer[38]; // this corresponds to the 32 src blocks #63946, #63948, #63949 ... #63978. - locs = parse_range(params.tokens[pos++]); - CHECK_EQ(src.size, locs.size); - CHECK_EQ(locs.pos.size() % 2, static_cast<size_t>(0)); + locs = RangeSet::Parse(params.tokens[pos++]); + CHECK_EQ(src.blocks(), locs.blocks()); } - LOG(INFO) << "printing hash in hex for " << src.size << " source blocks"; - for (size_t i = 0; i < src.size; i++) { - int block_num = src.get_block(i); - CHECK_NE(block_num, -1); - int buffer_index = locs.get_block(i); - CHECK_NE(buffer_index, -1); + LOG(INFO) << "printing hash in hex for " << src.blocks() << " source blocks"; + for (size_t i = 0; i < src.blocks(); i++) { + size_t block_num = src.GetBlockNumber(i); + size_t buffer_index = locs.GetBlockNumber(i); CHECK_LE((buffer_index + 1) * BLOCKSIZE, buffer.size()); uint8_t digest[SHA_DIGEST_LENGTH]; @@ -503,11 +426,10 @@ static void PrintHashForCorruptedStashedBlocks(const std::string& id, const std::vector<uint8_t>& buffer, const RangeSet& src) { LOG(INFO) << "printing hash in hex for stash_id: " << id; - CHECK_EQ(src.size * BLOCKSIZE, buffer.size()); + CHECK_EQ(src.blocks() * BLOCKSIZE, buffer.size()); - for (size_t i = 0; i < src.size; i++) { - int block_num = src.get_block(i); - CHECK_NE(block_num, -1); + for (size_t i = 0; i < src.blocks(); i++) { + size_t block_num = src.GetBlockNumber(i); uint8_t digest[SHA_DIGEST_LENGTH]; SHA1(buffer.data() + i * BLOCKSIZE, BLOCKSIZE, digest); @@ -526,7 +448,7 @@ static void PrintHashForMissingStashedBlocks(const std::string& id, int fd) { LOG(INFO) << "print hash in hex for source blocks in missing stash: " << id; const RangeSet& src = stash_map[id]; - std::vector<uint8_t> buffer(src.size * BLOCKSIZE); + std::vector<uint8_t> buffer(src.blocks() * BLOCKSIZE); if (ReadBlocks(src, buffer, fd) == -1) { LOG(ERROR) << "failed to read source blocks for stash: " << id; return; @@ -618,85 +540,81 @@ static void DeleteStash(const std::string& base) { static int LoadStash(CommandParameters& params, const std::string& id, bool verify, size_t* blocks, std::vector<uint8_t>& buffer, bool printnoent) { - // In verify mode, if source range_set was saved for the given hash, - // check contents in the source blocks first. If the check fails, - // search for the stashed files on /cache as usual. - if (!params.canwrite) { - if (stash_map.find(id) != stash_map.end()) { - const RangeSet& src = stash_map[id]; - allocate(src.size * BLOCKSIZE, buffer); - - if (ReadBlocks(src, buffer, params.fd) == -1) { - LOG(ERROR) << "failed to read source blocks in stash map."; - return -1; - } - if (VerifyBlocks(id, buffer, src.size, true) != 0) { - LOG(ERROR) << "failed to verify loaded source blocks in stash map."; - PrintHashForCorruptedStashedBlocks(id, buffer, src); - return -1; - } - return 0; - } - } - - size_t blockcount = 0; + // In verify mode, if source range_set was saved for the given hash, check contents in the source + // blocks first. If the check fails, search for the stashed files on /cache as usual. + if (!params.canwrite) { + if (stash_map.find(id) != stash_map.end()) { + const RangeSet& src = stash_map[id]; + allocate(src.blocks() * BLOCKSIZE, buffer); - if (!blocks) { - blocks = &blockcount; + if (ReadBlocks(src, buffer, params.fd) == -1) { + LOG(ERROR) << "failed to read source blocks in stash map."; + return -1; + } + if (VerifyBlocks(id, buffer, src.blocks(), true) != 0) { + LOG(ERROR) << "failed to verify loaded source blocks in stash map."; + PrintHashForCorruptedStashedBlocks(id, buffer, src); + return -1; + } + return 0; } + } - std::string fn = GetStashFileName(params.stashbase, id, ""); + size_t blockcount = 0; + if (!blocks) { + blocks = &blockcount; + } - struct stat sb; - int res = stat(fn.c_str(), &sb); + std::string fn = GetStashFileName(params.stashbase, id, ""); - if (res == -1) { - if (errno != ENOENT || printnoent) { - PLOG(ERROR) << "stat \"" << fn << "\" failed"; - PrintHashForMissingStashedBlocks(id, params.fd); - } - return -1; + struct stat sb; + if (stat(fn.c_str(), &sb) == -1) { + if (errno != ENOENT || printnoent) { + PLOG(ERROR) << "stat \"" << fn << "\" failed"; + PrintHashForMissingStashedBlocks(id, params.fd); } + return -1; + } - LOG(INFO) << " loading " << fn; + LOG(INFO) << " loading " << fn; - if ((sb.st_size % BLOCKSIZE) != 0) { - LOG(ERROR) << fn << " size " << sb.st_size << " not multiple of block size " << BLOCKSIZE; - return -1; - } + if ((sb.st_size % BLOCKSIZE) != 0) { + LOG(ERROR) << fn << " size " << sb.st_size << " not multiple of block size " << BLOCKSIZE; + return -1; + } - android::base::unique_fd fd(TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_RDONLY))); - if (fd == -1) { - PLOG(ERROR) << "open \"" << fn << "\" failed"; - return -1; - } + android::base::unique_fd fd(TEMP_FAILURE_RETRY(ota_open(fn.c_str(), O_RDONLY))); + if (fd == -1) { + PLOG(ERROR) << "open \"" << fn << "\" failed"; + return -1; + } - allocate(sb.st_size, buffer); + allocate(sb.st_size, buffer); - if (read_all(fd, buffer, sb.st_size) == -1) { - return -1; - } + if (read_all(fd, buffer, sb.st_size) == -1) { + return -1; + } - *blocks = sb.st_size / BLOCKSIZE; + *blocks = sb.st_size / BLOCKSIZE; - if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) { - LOG(ERROR) << "unexpected contents in " << fn; - if (stash_map.find(id) == stash_map.end()) { - LOG(ERROR) << "failed to find source blocks number for stash " << id - << " when executing command: " << params.cmdname; - } else { - const RangeSet& src = stash_map[id]; - PrintHashForCorruptedStashedBlocks(id, buffer, src); - } - DeleteFile(fn); - return -1; + if (verify && VerifyBlocks(id, buffer, *blocks, true) != 0) { + LOG(ERROR) << "unexpected contents in " << fn; + if (stash_map.find(id) == stash_map.end()) { + LOG(ERROR) << "failed to find source blocks number for stash " << id + << " when executing command: " << params.cmdname; + } else { + const RangeSet& src = stash_map[id]; + PrintHashForCorruptedStashedBlocks(id, buffer, src); } + DeleteFile(fn); + return -1; + } - return 0; + return 0; } static int WriteStash(const std::string& base, const std::string& id, int blocks, - std::vector<uint8_t>& buffer, bool checkspace, bool *exists) { + std::vector<uint8_t>& buffer, bool checkspace, bool* exists) { if (base.empty()) { return -1; } @@ -866,113 +784,96 @@ static int FreeStash(const std::string& base, const std::string& id) { return 0; } +// Source contains packed data, which we want to move to the locations given in locs in the dest +// buffer. source and dest may be the same buffer. static void MoveRange(std::vector<uint8_t>& dest, const RangeSet& locs, - const std::vector<uint8_t>& source) { - // source contains packed data, which we want to move to the - // locations given in locs in the dest buffer. source and dest - // may be the same buffer. - - const uint8_t* from = source.data(); - uint8_t* to = dest.data(); - size_t start = locs.size; - for (int i = locs.count-1; i >= 0; --i) { - size_t blocks = locs.pos[i*2+1] - locs.pos[i*2]; - start -= blocks; - memmove(to + (locs.pos[i*2] * BLOCKSIZE), from + (start * BLOCKSIZE), - blocks * BLOCKSIZE); - } + const std::vector<uint8_t>& source) { + const uint8_t* from = source.data(); + uint8_t* to = dest.data(); + size_t start = locs.blocks(); + // Must do the movement backward. + for (auto it = locs.crbegin(); it != locs.crend(); it++) { + size_t blocks = it->second - it->first; + start -= blocks; + memmove(to + (it->first * BLOCKSIZE), from + (start * BLOCKSIZE), blocks * BLOCKSIZE); + } } -// Do a source/target load for move/bsdiff/imgdiff in version 2. -// We expect to parse the remainder of the parameter tokens as one of: -// -// <tgt_range> <src_block_count> <src_range> -// (loads data from source image only) -// -// <tgt_range> <src_block_count> - <[stash_id:stash_range] ...> -// (loads data from stashes only) -// -// <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...> -// (loads data from both source image and stashes) -// -// On return, params.buffer is filled with the loaded source data (rearranged and combined with -// stashed data as necessary). buffer may be reallocated if needed to accommodate the source data. -// *tgt is the target RangeSet. Any stashes required are loaded using LoadStash. - -static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& src_blocks, - bool* overlap) { - - // At least it needs to provide three parameters: <tgt_range>, - // <src_block_count> and "-"/<src_range>. - if (params.cpos + 2 >= params.tokens.size()) { - LOG(ERROR) << "invalid parameters"; - return -1; - } - - // <tgt_range> - tgt = parse_range(params.tokens[params.cpos++]); - - // <src_block_count> - const std::string& token = params.tokens[params.cpos++]; - if (!android::base::ParseUint(token.c_str(), &src_blocks)) { - LOG(ERROR) << "invalid src_block_count \"" << token << "\""; - return -1; - } - - allocate(src_blocks * BLOCKSIZE, params.buffer); - - // "-" or <src_range> [<src_loc>] - if (params.tokens[params.cpos] == "-") { - // no source ranges, only stashes - params.cpos++; - } else { - RangeSet src = parse_range(params.tokens[params.cpos++]); - int res = ReadBlocks(src, params.buffer, params.fd); - - if (overlap) { - *overlap = range_overlaps(src, tgt); - } +/** + * We expect to parse the remainder of the parameter tokens as one of: + * + * <src_block_count> <src_range> + * (loads data from source image only) + * + * <src_block_count> - <[stash_id:stash_range] ...> + * (loads data from stashes only) + * + * <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...> + * (loads data from both source image and stashes) + * + * On return, params.buffer is filled with the loaded source data (rearranged and combined with + * stashed data as necessary). buffer may be reallocated if needed to accommodate the source data. + * tgt is the target RangeSet for detecting overlaps. Any stashes required are loaded using + * LoadStash. + */ +static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size_t* src_blocks, + bool* overlap) { + CHECK(src_blocks != nullptr); + CHECK(overlap != nullptr); + + // <src_block_count> + const std::string& token = params.tokens[params.cpos++]; + if (!android::base::ParseUint(token, src_blocks)) { + LOG(ERROR) << "invalid src_block_count \"" << token << "\""; + return -1; + } - if (res == -1) { - return -1; - } + allocate(*src_blocks * BLOCKSIZE, params.buffer); - if (params.cpos >= params.tokens.size()) { - // no stashes, only source range - return 0; - } + // "-" or <src_range> [<src_loc>] + if (params.tokens[params.cpos] == "-") { + // no source ranges, only stashes + params.cpos++; + } else { + RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]); + *overlap = src.Overlaps(tgt); - RangeSet locs = parse_range(params.tokens[params.cpos++]); - MoveRange(params.buffer, locs, params.buffer); + if (ReadBlocks(src, params.buffer, params.fd) == -1) { + return -1; } - // <[stash_id:stash_range]> - while (params.cpos < params.tokens.size()) { - // Each word is a an index into the stash table, a colon, and - // then a rangeset describing where in the source block that - // stashed data should go. - std::vector<std::string> tokens = android::base::Split(params.tokens[params.cpos++], ":"); - if (tokens.size() != 2) { - LOG(ERROR) << "invalid parameter"; - return -1; - } - - std::vector<uint8_t> stash; - int res = LoadStash(params, tokens[0], false, nullptr, stash, true); + if (params.cpos >= params.tokens.size()) { + // no stashes, only source range + return 0; + } - if (res == -1) { - // These source blocks will fail verification if used later, but we - // will let the caller decide if this is a fatal failure - LOG(ERROR) << "failed to load stash " << tokens[0]; - continue; - } + RangeSet locs = RangeSet::Parse(params.tokens[params.cpos++]); + MoveRange(params.buffer, locs, params.buffer); + } - RangeSet locs = parse_range(tokens[1]); + // <[stash_id:stash_range]> + while (params.cpos < params.tokens.size()) { + // Each word is a an index into the stash table, a colon, and then a RangeSet describing where + // in the source block that stashed data should go. + std::vector<std::string> tokens = android::base::Split(params.tokens[params.cpos++], ":"); + if (tokens.size() != 2) { + LOG(ERROR) << "invalid parameter"; + return -1; + } - MoveRange(params.buffer, locs, stash); + std::vector<uint8_t> stash; + if (LoadStash(params, tokens[0], false, nullptr, stash, true) == -1) { + // These source blocks will fail verification if used later, but we + // will let the caller decide if this is a fatal failure + LOG(ERROR) << "failed to load stash " << tokens[0]; + continue; } - return 0; + RangeSet locs = RangeSet::Parse(tokens[1]); + MoveRange(params.buffer, locs, stash); + } + + return 0; } /** @@ -989,9 +890,8 @@ static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& * <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...> * (loads data from both source image and stashes) * - * Parameters are the same as for LoadSrcTgtVersion2, except for 'onehash', which tells the function - * whether to expect separate source and targe block hashes, or if they are both the same and only - * one hash should be expected, and 'isunresumable', which receives a non-zero value if block + * 'onehash' tells whether to expect separate source and targe block hashes, or if they are both the + * same and only one hash should be expected. params.isunresumable will be set to true if block * verification fails in a way that the update cannot be resumed anymore. * * If the function is unable to load the necessary blocks or their contents don't match the hashes, @@ -1002,87 +902,100 @@ static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& * * If the return value is 0, source blocks have expected content and the command can be performed. */ -static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t& src_blocks, - bool onehash, bool& overlap) { +static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* src_blocks, + bool onehash, bool* overlap) { + CHECK(src_blocks != nullptr); + CHECK(overlap != nullptr); + + if (params.cpos >= params.tokens.size()) { + LOG(ERROR) << "missing source hash"; + return -1; + } + + std::string srchash = params.tokens[params.cpos++]; + std::string tgthash; + + if (onehash) { + tgthash = srchash; + } else { if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing source hash"; - return -1; + LOG(ERROR) << "missing target hash"; + return -1; } + tgthash = params.tokens[params.cpos++]; + } - std::string srchash = params.tokens[params.cpos++]; - std::string tgthash; + // At least it needs to provide three parameters: <tgt_range>, <src_block_count> and + // "-"/<src_range>. + if (params.cpos + 2 >= params.tokens.size()) { + LOG(ERROR) << "invalid parameters"; + return -1; + } - if (onehash) { - tgthash = srchash; - } else { - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing target hash"; - return -1; - } - tgthash = params.tokens[params.cpos++]; - } + // <tgt_range> + tgt = RangeSet::Parse(params.tokens[params.cpos++]); - if (LoadSrcTgtVersion2(params, tgt, src_blocks, &overlap) == -1) { - return -1; - } + std::vector<uint8_t> tgtbuffer(tgt.blocks() * BLOCKSIZE); + if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) { + return -1; + } + + // Return now if target blocks already have expected content. + if (VerifyBlocks(tgthash, tgtbuffer, tgt.blocks(), false) == 0) { + return 1; + } - std::vector<uint8_t> tgtbuffer(tgt.size * BLOCKSIZE); + // Load source blocks. + if (LoadSourceBlocks(params, tgt, src_blocks, overlap) == -1) { + return -1; + } - if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) { + if (VerifyBlocks(srchash, params.buffer, *src_blocks, true) == 0) { + // If source and target blocks overlap, stash the source blocks so we can + // resume from possible write errors. In verify mode, we can skip stashing + // because the source blocks won't be overwritten. + if (*overlap && params.canwrite) { + LOG(INFO) << "stashing " << *src_blocks << " overlapping blocks to " << srchash; + + bool stash_exists = false; + if (WriteStash(params.stashbase, srchash, *src_blocks, params.buffer, true, + &stash_exists) != 0) { + LOG(ERROR) << "failed to stash overlapping source blocks"; return -1; - } + } - if (VerifyBlocks(tgthash, tgtbuffer, tgt.size, false) == 0) { - // Target blocks already have expected content, command should be skipped. - return 1; + params.stashed += *src_blocks; + // Can be deleted when the write has completed. + if (!stash_exists) { + params.freestash = srchash; + } } - if (VerifyBlocks(srchash, params.buffer, src_blocks, true) == 0) { - // If source and target blocks overlap, stash the source blocks so we can - // resume from possible write errors. In verify mode, we can skip stashing - // because the source blocks won't be overwritten. - if (overlap && params.canwrite) { - LOG(INFO) << "stashing " << src_blocks << " overlapping blocks to " << srchash; - - bool stash_exists = false; - if (WriteStash(params.stashbase, srchash, src_blocks, params.buffer, true, - &stash_exists) != 0) { - LOG(ERROR) << "failed to stash overlapping source blocks"; - return -1; - } - - params.stashed += src_blocks; - // Can be deleted when the write has completed. - if (!stash_exists) { - params.freestash = srchash; - } - } - - // Source blocks have expected content, command can proceed. - return 0; - } + // Source blocks have expected content, command can proceed. + return 0; + } - if (overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) { - // Overlapping source blocks were previously stashed, command can proceed. - // We are recovering from an interrupted command, so we don't know if the - // stash can safely be deleted after this command. - return 0; - } + if (*overlap && LoadStash(params, srchash, true, nullptr, params.buffer, true) == 0) { + // Overlapping source blocks were previously stashed, command can proceed. We are recovering + // from an interrupted command, so we don't know if the stash can safely be deleted after this + // command. + return 0; + } - // Valid source data not available, update cannot be resumed. - LOG(ERROR) << "partition has unexpected contents"; - PrintHashForCorruptedSourceBlocks(params, params.buffer); + // Valid source data not available, update cannot be resumed. + LOG(ERROR) << "partition has unexpected contents"; + PrintHashForCorruptedSourceBlocks(params, params.buffer); - params.isunresumable = true; + params.isunresumable = true; - return -1; + return -1; } static int PerformCommandMove(CommandParameters& params) { size_t blocks = 0; bool overlap = false; RangeSet tgt; - int status = LoadSrcTgtVersion3(params, tgt, blocks, true, overlap); + int status = LoadSrcTgtVersion3(params, tgt, &blocks, true, &overlap); if (status == -1) { LOG(ERROR) << "failed to read blocks for move"; @@ -1112,7 +1025,7 @@ static int PerformCommandMove(CommandParameters& params) { params.freestash.clear(); } - params.written += tgt.size; + params.written += tgt.blocks(); return 0; } @@ -1132,13 +1045,13 @@ static int PerformCommandStash(CommandParameters& params) { return 0; } - RangeSet src = parse_range(params.tokens[params.cpos++]); + RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]); - allocate(src.size * BLOCKSIZE, params.buffer); + allocate(src.blocks() * BLOCKSIZE, params.buffer); if (ReadBlocks(src, params.buffer, params.fd) == -1) { return -1; } - blocks = src.size; + blocks = src.blocks(); stash_map[id] = src; if (VerifyBlocks(id, params.buffer, blocks, true) != 0) { @@ -1177,220 +1090,202 @@ static int PerformCommandFree(CommandParameters& params) { } static int PerformCommandZero(CommandParameters& params) { + if (params.cpos >= params.tokens.size()) { + LOG(ERROR) << "missing target blocks for zero"; + return -1; + } - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing target blocks for zero"; - return -1; - } + RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]); - RangeSet tgt = parse_range(params.tokens[params.cpos++]); + LOG(INFO) << " zeroing " << tgt.blocks() << " blocks"; - LOG(INFO) << " zeroing " << tgt.size << " blocks"; + allocate(BLOCKSIZE, params.buffer); + memset(params.buffer.data(), 0, BLOCKSIZE); - allocate(BLOCKSIZE, params.buffer); - memset(params.buffer.data(), 0, BLOCKSIZE); + if (params.canwrite) { + for (const auto& range : tgt) { + off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE; + size_t size = (range.second - range.first) * BLOCKSIZE; + if (!discard_blocks(params.fd, offset, size)) { + return -1; + } - if (params.canwrite) { - for (size_t i = 0; i < tgt.count; ++i) { - off64_t offset = static_cast<off64_t>(tgt.pos[i * 2]) * BLOCKSIZE; - size_t size = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * BLOCKSIZE; - if (!discard_blocks(params.fd, offset, size)) { - return -1; - } - - if (!check_lseek(params.fd, offset, SEEK_SET)) { - return -1; - } - - for (size_t j = tgt.pos[i * 2]; j < tgt.pos[i * 2 + 1]; ++j) { - if (write_all(params.fd, params.buffer, BLOCKSIZE) == -1) { - return -1; - } - } + if (!check_lseek(params.fd, offset, SEEK_SET)) { + return -1; + } + + for (size_t j = range.first; j < range.second; ++j) { + if (write_all(params.fd, params.buffer, BLOCKSIZE) == -1) { + return -1; } + } } + } - if (params.cmdname[0] == 'z') { - // Update only for the zero command, as the erase command will call - // this if DEBUG_ERASE is defined. - params.written += tgt.size; - } + if (params.cmdname[0] == 'z') { + // Update only for the zero command, as the erase command will call + // this if DEBUG_ERASE is defined. + params.written += tgt.blocks(); + } - return 0; + return 0; } static int PerformCommandNew(CommandParameters& params) { + if (params.cpos >= params.tokens.size()) { + LOG(ERROR) << "missing target blocks for new"; + return -1; + } - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing target blocks for new"; - return -1; - } - - RangeSet tgt = parse_range(params.tokens[params.cpos++]); - - if (params.canwrite) { - LOG(INFO) << " writing " << tgt.size << " blocks of new data"; - - RangeSinkState rss(tgt); - rss.fd = params.fd; - rss.p_block = 0; - rss.p_remain = (tgt.pos[1] - tgt.pos[0]) * BLOCKSIZE; - - off64_t offset = static_cast<off64_t>(tgt.pos[0]) * BLOCKSIZE; - if (!discard_blocks(params.fd, offset, tgt.size * BLOCKSIZE)) { - return -1; - } - - if (!check_lseek(params.fd, offset, SEEK_SET)) { - return -1; - } + RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]); - pthread_mutex_lock(¶ms.nti.mu); - params.nti.rss = &rss; - pthread_cond_broadcast(¶ms.nti.cv); + if (params.canwrite) { + LOG(INFO) << " writing " << tgt.blocks() << " blocks of new data"; - while (params.nti.rss) { - pthread_cond_wait(¶ms.nti.cv, ¶ms.nti.mu); - } + RangeSinkWriter writer(params.fd, tgt); + pthread_mutex_lock(¶ms.nti.mu); + params.nti.writer = &writer; + pthread_cond_broadcast(¶ms.nti.cv); + while (params.nti.writer != nullptr) { + if (!params.nti.receiver_available) { + LOG(ERROR) << "missing " << (tgt.blocks() * BLOCKSIZE - params.nti.writer->BytesWritten()) + << " bytes of new data"; pthread_mutex_unlock(¶ms.nti.mu); + return -1; + } + pthread_cond_wait(¶ms.nti.cv, ¶ms.nti.mu); } - params.written += tgt.size; + pthread_mutex_unlock(¶ms.nti.mu); + } - return 0; + params.written += tgt.blocks(); + + return 0; } static int PerformCommandDiff(CommandParameters& params) { + // <offset> <length> + if (params.cpos + 1 >= params.tokens.size()) { + LOG(ERROR) << "missing patch offset or length for " << params.cmdname; + return -1; + } - // <offset> <length> - if (params.cpos + 1 >= params.tokens.size()) { - LOG(ERROR) << "missing patch offset or length for " << params.cmdname; - return -1; - } + size_t offset; + if (!android::base::ParseUint(params.tokens[params.cpos++], &offset)) { + LOG(ERROR) << "invalid patch offset"; + return -1; + } - size_t offset; - if (!android::base::ParseUint(params.tokens[params.cpos++].c_str(), &offset)) { - LOG(ERROR) << "invalid patch offset"; - return -1; - } + size_t len; + if (!android::base::ParseUint(params.tokens[params.cpos++], &len)) { + LOG(ERROR) << "invalid patch len"; + return -1; + } - size_t len; - if (!android::base::ParseUint(params.tokens[params.cpos++].c_str(), &len)) { - LOG(ERROR) << "invalid patch len"; - return -1; - } + RangeSet tgt; + size_t blocks = 0; + bool overlap = false; + int status = LoadSrcTgtVersion3(params, tgt, &blocks, false, &overlap); - RangeSet tgt; - size_t blocks = 0; - bool overlap = false; - int status = LoadSrcTgtVersion3(params, tgt, blocks, false, overlap); + if (status == -1) { + LOG(ERROR) << "failed to read blocks for diff"; + return -1; + } - if (status == -1) { - LOG(ERROR) << "failed to read blocks for diff"; - return -1; - } + if (status == 0) { + params.foundwrites = true; + } else if (params.foundwrites) { + LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]"; + } + if (params.canwrite) { if (status == 0) { - params.foundwrites = true; - } else if (params.foundwrites) { - LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]"; - } - - if (params.canwrite) { - if (status == 0) { - LOG(INFO) << "patching " << blocks << " blocks to " << tgt.size; - Value patch_value(VAL_BLOB, - std::string(reinterpret_cast<const char*>(params.patch_start + offset), len)); - RangeSinkState rss(tgt); - rss.fd = params.fd; - rss.p_block = 0; - rss.p_remain = (tgt.pos[1] - tgt.pos[0]) * BLOCKSIZE; - - off64_t offset = static_cast<off64_t>(tgt.pos[0]) * BLOCKSIZE; - if (!discard_blocks(params.fd, offset, rss.p_remain)) { - return -1; - } - - if (!check_lseek(params.fd, offset, SEEK_SET)) { - return -1; - } - - if (params.cmdname[0] == 'i') { // imgdiff - if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, - &RangeSinkWrite, &rss, nullptr, nullptr) != 0) { - LOG(ERROR) << "Failed to apply image patch."; - return -1; - } - } else { - if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, - 0, &RangeSinkWrite, &rss, nullptr) != 0) { - LOG(ERROR) << "Failed to apply bsdiff patch."; - return -1; - } - } - - // We expect the output of the patcher to fill the tgt ranges exactly. - if (rss.p_block != tgt.count || rss.p_remain != 0) { - LOG(ERROR) << "range sink underrun?"; - } - } else { - LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.size - << " [" << params.cmdline << "]"; + LOG(INFO) << "patching " << blocks << " blocks to " << tgt.blocks(); + Value patch_value( + VAL_BLOB, std::string(reinterpret_cast<const char*>(params.patch_start + offset), len)); + + RangeSinkWriter writer(params.fd, tgt); + if (params.cmdname[0] == 'i') { // imgdiff + if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, + std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1, + std::placeholders::_2), + nullptr, nullptr) != 0) { + LOG(ERROR) << "Failed to apply image patch."; + return -1; } - } + } else { + if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, &patch_value, 0, + std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1, + std::placeholders::_2), + nullptr) != 0) { + LOG(ERROR) << "Failed to apply bsdiff patch."; + return -1; + } + } - if (!params.freestash.empty()) { - FreeStash(params.stashbase, params.freestash); - params.freestash.clear(); + // We expect the output of the patcher to fill the tgt ranges exactly. + if (!writer.Finished()) { + LOG(ERROR) << "range sink underrun?"; + } + } else { + LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.blocks() << " [" + << params.cmdline << "]"; } + } - params.written += tgt.size; + if (!params.freestash.empty()) { + FreeStash(params.stashbase, params.freestash); + params.freestash.clear(); + } - return 0; + params.written += tgt.blocks(); + + return 0; } static int PerformCommandErase(CommandParameters& params) { - if (DEBUG_ERASE) { - return PerformCommandZero(params); - } + if (DEBUG_ERASE) { + return PerformCommandZero(params); + } - struct stat sb; - if (fstat(params.fd, &sb) == -1) { - PLOG(ERROR) << "failed to fstat device to erase"; - return -1; - } + struct stat sb; + if (fstat(params.fd, &sb) == -1) { + PLOG(ERROR) << "failed to fstat device to erase"; + return -1; + } - if (!S_ISBLK(sb.st_mode)) { - LOG(ERROR) << "not a block device; skipping erase"; - return -1; - } + if (!S_ISBLK(sb.st_mode)) { + LOG(ERROR) << "not a block device; skipping erase"; + return -1; + } - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing target blocks for erase"; - return -1; - } + if (params.cpos >= params.tokens.size()) { + LOG(ERROR) << "missing target blocks for erase"; + return -1; + } - RangeSet tgt = parse_range(params.tokens[params.cpos++]); + RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]); - if (params.canwrite) { - LOG(INFO) << " erasing " << tgt.size << " blocks"; - - for (size_t i = 0; i < tgt.count; ++i) { - uint64_t blocks[2]; - // offset in bytes - blocks[0] = tgt.pos[i * 2] * (uint64_t) BLOCKSIZE; - // length in bytes - blocks[1] = (tgt.pos[i * 2 + 1] - tgt.pos[i * 2]) * (uint64_t) BLOCKSIZE; - - if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) { - PLOG(ERROR) << "BLKDISCARD ioctl failed"; - return -1; - } - } + if (params.canwrite) { + LOG(INFO) << " erasing " << tgt.blocks() << " blocks"; + + for (const auto& range : tgt) { + uint64_t blocks[2]; + // offset in bytes + blocks[0] = range.first * static_cast<uint64_t>(BLOCKSIZE); + // length in bytes + blocks[1] = (range.second - range.first) * static_cast<uint64_t>(BLOCKSIZE); + + if (ioctl(params.fd, BLKDISCARD, &blocks) == -1) { + PLOG(ERROR) << "BLKDISCARD ioctl failed"; + return -1; + } } + } - return 0; + return 0; } // Definitions for transfer list command functions @@ -1429,10 +1324,10 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, return nullptr; } - const Value* blockdev_filename = args[0].get(); - const Value* transfer_list_value = args[1].get(); - const Value* new_data_fn = args[2].get(); - const Value* patch_data_fn = args[3].get(); + const std::unique_ptr<Value>& blockdev_filename = args[0]; + const std::unique_ptr<Value>& transfer_list_value = args[1]; + const std::unique_ptr<Value>& new_data_fn = args[2]; + const std::unique_ptr<Value>& patch_data_fn = args[3]; if (blockdev_filename->type != VAL_STRING) { ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name); @@ -1487,6 +1382,7 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, if (params.canwrite) { params.nti.za = za; params.nti.entry = new_entry; + params.nti.receiver_available = true; pthread_mutex_init(¶ms.nti.mu, nullptr); pthread_cond_init(¶ms.nti.cv, nullptr); @@ -1722,64 +1618,62 @@ Value* BlockImageUpdateFn(const char* name, State* state, } Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { - if (argv.size() != 2) { - ErrorAbort(state, kArgsParsingFailure, "range_sha1 expects 2 arguments, got %zu", - argv.size()); - return StringValue(""); - } + if (argv.size() != 2) { + ErrorAbort(state, kArgsParsingFailure, "range_sha1 expects 2 arguments, got %zu", argv.size()); + return StringValue(""); + } - std::vector<std::unique_ptr<Value>> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } + std::vector<std::unique_ptr<Value>> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; + } - const Value* blockdev_filename = args[0].get(); - const Value* ranges = args[1].get(); + const std::unique_ptr<Value>& blockdev_filename = args[0]; + const std::unique_ptr<Value>& ranges = args[1]; - if (blockdev_filename->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", - name); - return StringValue(""); - } - if (ranges->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); - return StringValue(""); - } + if (blockdev_filename->type != VAL_STRING) { + ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name); + return StringValue(""); + } + if (ranges->type != VAL_STRING) { + ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); + return StringValue(""); + } - android::base::unique_fd fd(ota_open(blockdev_filename->data.c_str(), O_RDWR)); - if (fd == -1) { - ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", - blockdev_filename->data.c_str(), strerror(errno)); - return StringValue(""); - } + android::base::unique_fd fd(ota_open(blockdev_filename->data.c_str(), O_RDWR)); + if (fd == -1) { + ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", blockdev_filename->data.c_str(), + strerror(errno)); + return StringValue(""); + } - RangeSet rs = parse_range(ranges->data); + RangeSet rs = RangeSet::Parse(ranges->data); - SHA_CTX ctx; - SHA1_Init(&ctx); + SHA_CTX ctx; + SHA1_Init(&ctx); - std::vector<uint8_t> buffer(BLOCKSIZE); - for (size_t i = 0; i < rs.count; ++i) { - if (!check_lseek(fd, (off64_t)rs.pos[i*2] * BLOCKSIZE, SEEK_SET)) { - ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", - blockdev_filename->data.c_str(), strerror(errno)); - return StringValue(""); - } + std::vector<uint8_t> buffer(BLOCKSIZE); + for (const auto& range : rs) { + if (!check_lseek(fd, static_cast<off64_t>(range.first) * BLOCKSIZE, SEEK_SET)) { + ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", blockdev_filename->data.c_str(), + strerror(errno)); + return StringValue(""); + } - for (size_t j = rs.pos[i*2]; j < rs.pos[i*2+1]; ++j) { - if (read_all(fd, buffer, BLOCKSIZE) == -1) { - ErrorAbort(state, kFreadFailure, "failed to read %s: %s", - blockdev_filename->data.c_str(), strerror(errno)); - return StringValue(""); - } + for (size_t j = range.first; j < range.second; ++j) { + if (read_all(fd, buffer, BLOCKSIZE) == -1) { + ErrorAbort(state, kFreadFailure, "failed to read %s: %s", blockdev_filename->data.c_str(), + strerror(errno)); + return StringValue(""); + } - SHA1_Update(&ctx, buffer.data(), BLOCKSIZE); - } + SHA1_Update(&ctx, buffer.data(), BLOCKSIZE); } - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1_Final(digest, &ctx); + } + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1_Final(digest, &ctx); - return StringValue(print_sha1(digest)); + return StringValue(print_sha1(digest)); } // This function checks if a device has been remounted R/W prior to an incremental @@ -1789,145 +1683,140 @@ Value* RangeSha1Fn(const char* name, State* state, const std::vector<std::unique Value* CheckFirstBlockFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { - if (argv.size() != 1) { - ErrorAbort(state, kArgsParsingFailure, "check_first_block expects 1 argument, got %zu", - argv.size()); - return StringValue(""); - } + if (argv.size() != 1) { + ErrorAbort(state, kArgsParsingFailure, "check_first_block expects 1 argument, got %zu", + argv.size()); + return StringValue(""); + } - std::vector<std::unique_ptr<Value>> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } + std::vector<std::unique_ptr<Value>> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; + } - const Value* arg_filename = args[0].get(); + const std::unique_ptr<Value>& arg_filename = args[0]; - if (arg_filename->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); - return StringValue(""); - } + if (arg_filename->type != VAL_STRING) { + ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); + return StringValue(""); + } - android::base::unique_fd fd(ota_open(arg_filename->data.c_str(), O_RDONLY)); - if (fd == -1) { - ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", arg_filename->data.c_str(), - strerror(errno)); - return StringValue(""); - } + android::base::unique_fd fd(ota_open(arg_filename->data.c_str(), O_RDONLY)); + if (fd == -1) { + ErrorAbort(state, kFileOpenFailure, "open \"%s\" failed: %s", arg_filename->data.c_str(), + strerror(errno)); + return StringValue(""); + } - RangeSet blk0 {1 /*count*/, 1/*size*/, std::vector<size_t> {0, 1}/*position*/}; - std::vector<uint8_t> block0_buffer(BLOCKSIZE); + RangeSet blk0(std::vector<Range>{ Range{ 0, 1 } }); + std::vector<uint8_t> block0_buffer(BLOCKSIZE); - if (ReadBlocks(blk0, block0_buffer, fd) == -1) { - ErrorAbort(state, kFreadFailure, "failed to read %s: %s", arg_filename->data.c_str(), - strerror(errno)); - return StringValue(""); - } + if (ReadBlocks(blk0, block0_buffer, fd) == -1) { + ErrorAbort(state, kFreadFailure, "failed to read %s: %s", arg_filename->data.c_str(), + strerror(errno)); + return StringValue(""); + } - // https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout - // Super block starts from block 0, offset 0x400 - // 0x2C: len32 Mount time - // 0x30: len32 Write time - // 0x34: len16 Number of mounts since the last fsck - // 0x38: len16 Magic signature 0xEF53 + // https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout + // Super block starts from block 0, offset 0x400 + // 0x2C: len32 Mount time + // 0x30: len32 Write time + // 0x34: len16 Number of mounts since the last fsck + // 0x38: len16 Magic signature 0xEF53 - time_t mount_time = *reinterpret_cast<uint32_t*>(&block0_buffer[0x400+0x2C]); - uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400+0x34]); + time_t mount_time = *reinterpret_cast<uint32_t*>(&block0_buffer[0x400 + 0x2C]); + uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x34]); - if (mount_count > 0) { - uiPrintf(state, "Device was remounted R/W %d times\n", mount_count); - uiPrintf(state, "Last remount happened on %s", ctime(&mount_time)); - } + if (mount_count > 0) { + uiPrintf(state, "Device was remounted R/W %" PRIu16 " times", mount_count); + uiPrintf(state, "Last remount happened on %s", ctime(&mount_time)); + } - return StringValue("t"); + return StringValue("t"); } - Value* BlockImageRecoverFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { - if (argv.size() != 2) { - ErrorAbort(state, kArgsParsingFailure, "block_image_recover expects 2 arguments, got %zu", - argv.size()); - return StringValue(""); - } + if (argv.size() != 2) { + ErrorAbort(state, kArgsParsingFailure, "block_image_recover expects 2 arguments, got %zu", + argv.size()); + return StringValue(""); + } - std::vector<std::unique_ptr<Value>> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } + std::vector<std::unique_ptr<Value>> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; + } - const Value* filename = args[0].get(); - const Value* ranges = args[1].get(); + const std::unique_ptr<Value>& filename = args[0]; + const std::unique_ptr<Value>& ranges = args[1]; - if (filename->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); - return StringValue(""); - } - if (ranges->type != VAL_STRING) { - ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); - return StringValue(""); - } + if (filename->type != VAL_STRING) { + ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); + return StringValue(""); + } + if (ranges->type != VAL_STRING) { + ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); + return StringValue(""); + } - // Output notice to log when recover is attempted - LOG(INFO) << filename->data << " image corrupted, attempting to recover..."; + // Output notice to log when recover is attempted + LOG(INFO) << filename->data << " image corrupted, attempting to recover..."; - // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read - fec::io fh(filename->data.c_str(), O_RDWR); + // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read + fec::io fh(filename->data, O_RDWR); - if (!fh) { - ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(), - strerror(errno)); - return StringValue(""); - } + if (!fh) { + ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", filename->data.c_str(), + strerror(errno)); + return StringValue(""); + } - if (!fh.has_ecc() || !fh.has_verity()) { - ErrorAbort(state, kLibfecFailure, "unable to use metadata to correct errors"); - return StringValue(""); - } + if (!fh.has_ecc() || !fh.has_verity()) { + ErrorAbort(state, kLibfecFailure, "unable to use metadata to correct errors"); + return StringValue(""); + } + + fec_status status; + if (!fh.get_status(status)) { + ErrorAbort(state, kLibfecFailure, "failed to read FEC status"); + return StringValue(""); + } - fec_status status; + uint8_t buffer[BLOCKSIZE]; + for (const auto& range : RangeSet::Parse(ranges->data)) { + for (size_t j = range.first; j < range.second; ++j) { + // Stay within the data area, libfec validates and corrects metadata + if (status.data_size <= static_cast<uint64_t>(j) * BLOCKSIZE) { + continue; + } - if (!fh.get_status(status)) { - ErrorAbort(state, kLibfecFailure, "failed to read FEC status"); + if (fh.pread(buffer, BLOCKSIZE, static_cast<off64_t>(j) * BLOCKSIZE) != BLOCKSIZE) { + ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s", + filename->data.c_str(), j, strerror(errno)); return StringValue(""); - } + } - RangeSet rs = parse_range(ranges->data); - - uint8_t buffer[BLOCKSIZE]; - - for (size_t i = 0; i < rs.count; ++i) { - for (size_t j = rs.pos[i * 2]; j < rs.pos[i * 2 + 1]; ++j) { - // Stay within the data area, libfec validates and corrects metadata - if (status.data_size <= (uint64_t)j * BLOCKSIZE) { - continue; - } - - if (fh.pread(buffer, BLOCKSIZE, (off64_t)j * BLOCKSIZE) != BLOCKSIZE) { - ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s", - filename->data.c_str(), j, strerror(errno)); - return StringValue(""); - } - - // If we want to be able to recover from a situation where rewriting a corrected - // block doesn't guarantee the same data will be returned when re-read later, we - // can save a copy of corrected blocks to /cache. Note: - // - // 1. Maximum space required from /cache is the same as the maximum number of - // corrupted blocks we can correct. For RS(255, 253) and a 2 GiB partition, - // this would be ~16 MiB, for example. - // - // 2. To find out if this block was corrupted, call fec_get_status after each - // read and check if the errors field value has increased. - } + // If we want to be able to recover from a situation where rewriting a corrected + // block doesn't guarantee the same data will be returned when re-read later, we + // can save a copy of corrected blocks to /cache. Note: + // + // 1. Maximum space required from /cache is the same as the maximum number of + // corrupted blocks we can correct. For RS(255, 253) and a 2 GiB partition, + // this would be ~16 MiB, for example. + // + // 2. To find out if this block was corrupted, call fec_get_status after each + // read and check if the errors field value has increased. } - LOG(INFO) << "..." << filename->data << " image recovered successfully."; - return StringValue("t"); + } + LOG(INFO) << "..." << filename->data << " image recovered successfully."; + return StringValue("t"); } void RegisterBlockImageFunctions() { - RegisterFunction("block_image_verify", BlockImageVerifyFn); - RegisterFunction("block_image_update", BlockImageUpdateFn); - RegisterFunction("block_image_recover", BlockImageRecoverFn); - RegisterFunction("check_first_block", CheckFirstBlockFn); - RegisterFunction("range_sha1", RangeSha1Fn); + RegisterFunction("block_image_verify", BlockImageVerifyFn); + RegisterFunction("block_image_update", BlockImageUpdateFn); + RegisterFunction("block_image_recover", BlockImageRecoverFn); + RegisterFunction("check_first_block", CheckFirstBlockFn); + RegisterFunction("range_sha1", RangeSha1Fn); } diff --git a/updater/include/updater/rangeset.h b/updater/include/updater/rangeset.h new file mode 100644 index 000000000..fad038043 --- /dev/null +++ b/updater/include/updater/rangeset.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2017 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. + */ + +#pragma once + +#include <stddef.h> + +#include <string> +#include <utility> +#include <vector> + +#include <android-base/logging.h> +#include <android-base/parseint.h> +#include <android-base/strings.h> + +using Range = std::pair<size_t, size_t>; + +class RangeSet { + public: + RangeSet() : blocks_(0) {} + + explicit RangeSet(std::vector<Range>&& pairs) { + CHECK_NE(pairs.size(), static_cast<size_t>(0)) << "Invalid number of tokens"; + + // Sanity check the input. + size_t result = 0; + for (const auto& range : pairs) { + CHECK_LT(range.first, range.second) + << "Empty or negative range: " << range.first << ", " << range.second; + size_t sz = range.second - range.first; + CHECK_LE(result, SIZE_MAX - sz) << "RangeSet size overflow"; + result += sz; + } + + ranges_ = pairs; + blocks_ = result; + } + + static RangeSet Parse(const std::string& range_text) { + std::vector<std::string> pieces = android::base::Split(range_text, ","); + CHECK_GE(pieces.size(), static_cast<size_t>(3)) << "Invalid range text: " << range_text; + + size_t num; + CHECK(android::base::ParseUint(pieces[0], &num, static_cast<size_t>(INT_MAX))) + << "Failed to parse the number of tokens: " << range_text; + + CHECK_NE(num, static_cast<size_t>(0)) << "Invalid number of tokens: " << range_text; + CHECK_EQ(num % 2, static_cast<size_t>(0)) << "Number of tokens must be even: " << range_text; + CHECK_EQ(num, pieces.size() - 1) << "Mismatching number of tokens: " << range_text; + + std::vector<Range> pairs; + for (size_t i = 0; i < num; i += 2) { + size_t first; + CHECK(android::base::ParseUint(pieces[i + 1], &first, static_cast<size_t>(INT_MAX))); + size_t second; + CHECK(android::base::ParseUint(pieces[i + 2], &second, static_cast<size_t>(INT_MAX))); + + pairs.emplace_back(first, second); + } + + return RangeSet(std::move(pairs)); + } + + // Get the block number for the i-th (starting from 0) block in the RangeSet. + size_t GetBlockNumber(size_t idx) const { + CHECK_LT(idx, blocks_) << "Out of bound index " << idx << " (total blocks: " << blocks_ << ")"; + + for (const auto& range : ranges_) { + if (idx < range.second - range.first) { + return range.first + idx; + } + idx -= (range.second - range.first); + } + + CHECK(false) << "Failed to find block number for index " << idx; + return 0; // Unreachable, but to make compiler happy. + } + + // RangeSet has half-closed half-open bounds. For example, "3,5" contains blocks 3 and 4. So "3,5" + // and "5,7" are not overlapped. + bool Overlaps(const RangeSet& other) const { + for (const auto& range : ranges_) { + size_t start = range.first; + size_t end = range.second; + for (const auto& other_range : other.ranges_) { + size_t other_start = other_range.first; + size_t other_end = other_range.second; + // [start, end) vs [other_start, other_end) + if (!(other_start >= end || start >= other_end)) { + return true; + } + } + } + return false; + } + + // size() gives the number of Range's in this RangeSet. + size_t size() const { + return ranges_.size(); + } + + // blocks() gives the number of all blocks in this RangeSet. + size_t blocks() const { + return blocks_; + } + + // We provide const iterators only. + std::vector<Range>::const_iterator cbegin() const { + return ranges_.cbegin(); + } + + std::vector<Range>::const_iterator cend() const { + return ranges_.cend(); + } + + // Need to provide begin()/end() since range-based loop expects begin()/end(). + std::vector<Range>::const_iterator begin() const { + return ranges_.cbegin(); + } + + std::vector<Range>::const_iterator end() const { + return ranges_.cend(); + } + + // Reverse const iterators for MoveRange(). + std::vector<Range>::const_reverse_iterator crbegin() const { + return ranges_.crbegin(); + } + + std::vector<Range>::const_reverse_iterator crend() const { + return ranges_.crend(); + } + + const Range& operator[](size_t i) const { + return ranges_[i]; + } + + bool operator==(const RangeSet& other) const { + // The orders of Range's matter. "4,1,5,8,10" != "4,8,10,1,5". + return (ranges_ == other.ranges_); + } + + bool operator!=(const RangeSet& other) const { + return ranges_ != other.ranges_; + } + + private: + // Actual limit for each value and the total number are both INT_MAX. + std::vector<Range> ranges_; + size_t blocks_; +}; diff --git a/updater/install.cpp b/updater/install.cpp index f91f3fc9f..857d7f1e0 100644 --- a/updater/install.cpp +++ b/updater/install.cpp @@ -181,8 +181,8 @@ Value* MountFn(const char* name, State* state, const std::vector<std::unique_ptr if (mount(location.c_str(), mount_point.c_str(), fs_type.c_str(), MS_NOATIME | MS_NODEV | MS_NODIRATIME, mount_options.c_str()) < 0) { - uiPrintf(state, "%s: failed to mount %s at %s: %s\n", name, location.c_str(), - mount_point.c_str(), strerror(errno)); + uiPrintf(state, "%s: Failed to mount %s at %s: %s", name, location.c_str(), mount_point.c_str(), + strerror(errno)); return StringValue(""); } @@ -231,12 +231,12 @@ Value* UnmountFn(const char* name, State* state, const std::vector<std::unique_p scan_mounted_volumes(); MountedVolume* vol = find_mounted_volume_by_mount_point(mount_point.c_str()); if (vol == nullptr) { - uiPrintf(state, "unmount of %s failed; no such volume\n", mount_point.c_str()); + uiPrintf(state, "Failed to unmount %s: No such volume", mount_point.c_str()); return nullptr; } else { int ret = unmount_mounted_volume(vol); if (ret != 0) { - uiPrintf(state, "unmount of %s failed (%d): %s\n", mount_point.c_str(), ret, strerror(errno)); + uiPrintf(state, "Failed to unmount %s: %s", mount_point.c_str(), strerror(errno)); } } @@ -699,15 +699,15 @@ Value* ApplyPatchCheckFn(const char* name, State* state, const std::vector<std:: return StringValue(result == 0 ? "t" : ""); } -// This is the updater side handler for ui_print() in edify script. Contents -// will be sent over to the recovery side for on-screen display. +// This is the updater side handler for ui_print() in edify script. Contents will be sent over to +// the recovery side for on-screen display. Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv) { std::vector<std::string> args; if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); } - std::string buffer = android::base::Join(args, "") + "\n"; + std::string buffer = android::base::Join(args, ""); uiPrint(state, buffer); return StringValue(buffer); } |