Add the missing LOCAL_CFLAGS for librecovery.
am: 25dbe1717e
Change-Id: Ie69a29ae06c2f5081b0532cbc6c9c32898187bec
diff --git a/Android.mk b/Android.mk
index 2a33bbe..9e374de 100644
--- a/Android.mk
+++ b/Android.mk
@@ -33,9 +33,11 @@
# ===============================
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)
@@ -181,6 +183,7 @@
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 7be3fdb..51bf393 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 @@
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 @@
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 @@
}
// 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 @@
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 8ea68a1..0000000
--- 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 9920c2b..f75a2c6 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 @@
);
}
-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 @@
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 dcdb922..0000000
--- 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 adcc61f..7d8b736 100644
--- a/applypatch/imgpatch.cpp
+++ b/applypatch/imgpatch.cpp
@@ -43,12 +43,11 @@
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 @@
* 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 @@
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 @@
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 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 @@
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 4489dec..da55432 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 @@
// 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 @@
// 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 6549f79..07c6609 100644
--- a/applypatch/include/applypatch/imgpatch.h
+++ b/applypatch/include/applypatch/imgpatch.h
@@ -19,10 +19,11 @@
#include <sys/types.h>
-using SinkFn = ssize_t (*)(const unsigned char*, ssize_t, void*);
+#include <functional>
-int ApplyImagePatch(const unsigned char* old_data, ssize_t old_size,
- const unsigned char* patch_data, ssize_t patch_size,
- SinkFn sink, void* token);
+using SinkFn = std::function<size_t(const unsigned char*, size_t)>;
+
+int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data,
+ size_t patch_size, SinkFn sink);
#endif // _APPLYPATCH_IMGPATCH_H
diff --git a/applypatch/testdata/new.file b/applypatch/testdata/new.file
deleted file mode 100644
index cdeb8fd..0000000
--- a/applypatch/testdata/new.file
+++ /dev/null
Binary files differ
diff --git a/applypatch/testdata/old.file b/applypatch/testdata/old.file
deleted file mode 100644
index 166c873..0000000
--- a/applypatch/testdata/old.file
+++ /dev/null
Binary files differ
diff --git a/applypatch/testdata/patch.bsdiff b/applypatch/testdata/patch.bsdiff
deleted file mode 100644
index b78d385..0000000
--- a/applypatch/testdata/patch.bsdiff
+++ /dev/null
Binary files differ
diff --git a/boot_control/Android.mk b/boot_control/Android.mk
new file mode 100644
index 0000000..27e3d97
--- /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 0000000..ec97b6c
--- /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 d8086be..f91446b 100644
--- a/bootloader_message/bootloader_message.cpp
+++ b/bootloader_message/bootloader_message.cpp
@@ -117,6 +117,13 @@
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 bc5104d..2ffbfc9 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -180,6 +180,11 @@
#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 97205fe..b1685eb 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 @@
$$->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 7eef13e..de0b0c8 100644
--- a/minadbd/Android.mk
+++ b/minadbd/Android.mk
@@ -29,6 +29,7 @@
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 0000000..7ea235b
--- /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 f23376b..fbcbac0 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 @@
}
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 71c2c62..ec4cdb3 100644
--- a/otafault/Android.mk
+++ b/otafault/Android.mk
@@ -23,7 +23,12 @@
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 3a89bb5..faae527 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 1e433a7..a1f0d48 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -20,11 +20,12 @@
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 @@
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 @@
# 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 @@
-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 0000000..3999aa5
--- /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 3fee32d..0000000
--- 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 0357acc..b38bc71 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 2f64850..7d00a3d 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 4f2b816..3925236 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 @@
ASSERT_TRUE(success) << "uncrypt service is not available.";
- has_misc = parse_misc();
+ std::string err;
+ has_misc = !get_bootloader_message_blk_device(&err).empty();
+ }
+
+ 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;
+ }
+
+ // Trigger the setup-bcb service.
+ ASSERT_TRUE(android::base::SetProperty("ctl.start", isSetup ? "setup-bcb" : "clear-bcb"));
+
+ // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+ sleep(1);
+
+ 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<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);
+ }
+
+ // 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)));
+
+ ASSERT_EQ(0, close(sockfd));
+
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", isSetup ? "setup-bcb" : "clear-bcb"));
+
+ // 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;
+
+ if (isSetup) {
+ ASSERT_EQ("boot-recovery", std::string(boot.command));
+ ASSERT_EQ(message_in_bcb, std::string(boot.recovery));
+
+ // 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));
+
+ // 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)));
+ }
}
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;
- }
- 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));
-
- // Ack having received the status code.
- int code = 0;
- ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
-
- ASSERT_EQ(0, close(sockfd));
-
- ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
-
- // 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;
-
- ASSERT_EQ("boot-recovery", std::string(boot.command));
- ASSERT_EQ(message_in_bcb, std::string(boot.recovery));
-
- // 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));
-
- // Clear the BCB.
- ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+ SetupOrClearBcb(true, message, message_in_bcb);
}
TEST_F(UncryptTest, clear_bcb) {
- if (!has_misc) {
- GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
- return;
- }
+ SetupOrClearBcb(false, "", "");
+}
- // Trigger the clear-bcb service.
- ASSERT_TRUE(android::base::SetProperty("ctl.start", "clear-bcb"));
+TEST_F(UncryptTest, setup_bcb_wipe_ab) {
+ TemporaryFile wipe_package;
+ ASSERT_TRUE(android::base::WriteStringToFile(std::string(345, 'a'), wipe_package.path));
- // 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;
- }
- 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)));
-
- ASSERT_EQ(0, close(sockfd));
-
- ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
-
- // 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;
-
- // 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 5652ddf..dc4b5d7 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -607,3 +607,69 @@
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 4c06487..61ceadc 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -132,6 +132,51 @@
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(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
deleted file mode 100644
index f497ec0..0000000
--- a/tests/testdata/alter-footer.zip
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/alter-metadata.zip b/tests/testdata/alter-metadata.zip
deleted file mode 100644
index 1c71fbc..0000000
--- a/tests/testdata/alter-metadata.zip
+++ /dev/null
Binary files differ
diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp
new file mode 100644
index 0000000..3c6d77e
--- /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 c614ccc..d5c1704 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 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 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 @@
buffer.resize(size);
}
-struct RangeSinkState {
- explicit RangeSinkState(RangeSet& rs) : tgt(rs) { };
+/**
+ * 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));
+ };
- int fd;
- const RangeSet& tgt;
- size_t p_block;
- size_t p_remain;
+ bool Finished() const {
+ return next_range_ == tgt_.size() && current_range_left_ == 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;
+ }
+
+ size_t written = 0;
+ while (size > 0) {
+ // 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 (!check_lseek(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;
+ }
+
+ 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_;
};
-static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) {
- RangeSinkState* rss = reinterpret_cast<RangeSinkState*>(token);
-
- if (rss->p_remain == 0) {
- LOG(ERROR) << "range sink write overrun";
- return 0;
- }
-
- ssize_t written = 0;
- while (size > 0) {
- size_t write_now = size;
-
- if (rss->p_remain < write_now) {
- write_now = rss->p_remain;
- }
-
- if (write_all(rss->fd, data, write_now) == -1) {
- 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;
- }
- }
- }
-
- 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.
-
+/**
+ * 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);
-
- // 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;
-
- if (nti->rss->p_block == nti->rss->tgt.count) {
- // we have written all the bytes desired by this rss.
-
- pthread_mutex_lock(&nti->mu);
- nti->rss = nullptr;
- pthread_cond_broadcast(&nti->cv);
- 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);
- return true;
+ // 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->writer->Finished()) {
+ // We have written all the bytes desired by this writer.
+
+ pthread_mutex_lock(&nti->mu);
+ nti->writer = nullptr;
+ pthread_cond_broadcast(&nti->cv);
+ pthread_mutex_unlock(&nti->mu);
+ }
+ }
+
+ 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;
- }
-
- p += size;
+ size_t p = 0;
+ for (const auto& range : src) {
+ if (!check_lseek(fd, static_cast<off64_t>(range.first) * BLOCKSIZE, SEEK_SET)) {
+ return -1;
}
- return 0;
+ size_t size = (range.second - range.first) * BLOCKSIZE;
+ if (read_all(fd, buffer.data() + p, size) == -1) {
+ return -1;
+ }
+
+ 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;
- }
-
- if (write_all(fd, data + p, size) == -1) {
- return -1;
- }
-
- p += size;
+ 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;
}
- return 0;
+ if (!check_lseek(fd, offset, SEEK_SET)) {
+ return -1;
+ }
+
+ if (write_all(fd, buffer.data() + written, size) == -1) {
+ return -1;
+ }
+
+ written += size;
+ }
+
+ return 0;
}
// Parameters for transfer list command functions
@@ -463,31 +391,26 @@
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 @@
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 @@
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 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);
+ // 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 (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;
-
- if (!blocks) {
- blocks = &blockcount;
- }
-
- std::string fn = GetStashFileName(params.stashbase, id, "");
-
- struct stat sb;
- int res = stat(fn.c_str(), &sb);
-
- if (res == -1) {
- if (errno != ENOENT || printnoent) {
- PLOG(ERROR) << "stat \"" << fn << "\" failed";
- PrintHashForMissingStashedBlocks(id, params.fd);
- }
+ if (ReadBlocks(src, buffer, params.fd) == -1) {
+ LOG(ERROR) << "failed to read source blocks in stash map.";
return -1;
- }
-
- LOG(INFO) << " loading " << fn;
-
- if ((sb.st_size % BLOCKSIZE) != 0) {
- LOG(ERROR) << fn << " size " << sb.st_size << " not multiple of block size " << BLOCKSIZE;
+ }
+ 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;
}
+ }
- 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;
+ size_t blockcount = 0;
+ if (!blocks) {
+ blocks = &blockcount;
+ }
+
+ std::string fn = GetStashFileName(params.stashbase, id, "");
+
+ 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;
+ }
- allocate(sb.st_size, buffer);
+ LOG(INFO) << " loading " << fn;
- if (read_all(fd, buffer, sb.st_size) == -1) {
- 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;
+ }
+
+ allocate(sb.st_size, buffer);
+
+ if (read_all(fd, buffer, sb.st_size) == -1) {
+ return -1;
+ }
+
+ *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;
+ }
- *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;
- }
-
- 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 @@
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.
+/**
+ * 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);
-static int LoadSrcTgtVersion2(CommandParameters& params, RangeSet& tgt, size_t& src_blocks,
- bool* overlap) {
+ // <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;
+ }
- // 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;
+ 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 = RangeSet::Parse(params.tokens[params.cpos++]);
+ *overlap = src.Overlaps(tgt);
+
+ if (ReadBlocks(src, params.buffer, params.fd) == -1) {
+ 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;
+ if (params.cpos >= params.tokens.size()) {
+ // no stashes, only source range
+ return 0;
}
- allocate(src_blocks * BLOCKSIZE, params.buffer);
+ RangeSet locs = RangeSet::Parse(params.tokens[params.cpos++]);
+ MoveRange(params.buffer, locs, 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);
- }
-
- if (res == -1) {
- return -1;
- }
-
- if (params.cpos >= params.tokens.size()) {
- // no stashes, only source range
- return 0;
- }
-
- RangeSet locs = parse_range(params.tokens[params.cpos++]);
- MoveRange(params.buffer, locs, params.buffer);
+ // <[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;
}
- // <[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 (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 = parse_range(tokens[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 @@
* <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 @@
*
* 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) {
- if (params.cpos >= params.tokens.size()) {
- LOG(ERROR) << "missing source hash";
- return -1;
- }
+static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet& tgt, size_t* src_blocks,
+ bool onehash, bool* overlap) {
+ CHECK(src_blocks != nullptr);
+ CHECK(overlap != nullptr);
- std::string srchash = params.tokens[params.cpos++];
- std::string tgthash;
-
- if (onehash) {
- tgthash = srchash;
- } else {
- if (params.cpos >= params.tokens.size()) {
- LOG(ERROR) << "missing target hash";
- return -1;
- }
- tgthash = params.tokens[params.cpos++];
- }
-
- if (LoadSrcTgtVersion2(params, tgt, src_blocks, &overlap) == -1) {
- return -1;
- }
-
- std::vector<uint8_t> tgtbuffer(tgt.size * BLOCKSIZE);
-
- if (ReadBlocks(tgt, tgtbuffer, params.fd) == -1) {
- return -1;
- }
-
- if (VerifyBlocks(tgthash, tgtbuffer, tgt.size, false) == 0) {
- // Target blocks already have expected content, command should be skipped.
- return 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;
- }
-
- 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;
- }
-
- 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);
-
- params.isunresumable = true;
-
+ 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 target hash";
+ return -1;
+ }
+ tgthash = params.tokens[params.cpos++];
+ }
+
+ // 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 = RangeSet::Parse(params.tokens[params.cpos++]);
+
+ 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;
+ }
+
+ // Load source blocks.
+ if (LoadSourceBlocks(params, tgt, src_blocks, overlap) == -1) {
+ return -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;
+ }
+
+ 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;
+ }
+
+ 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);
+
+ params.isunresumable = true;
+
+ 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 @@
params.freestash.clear();
}
- params.written += tgt.size;
+ params.written += tgt.blocks();
return 0;
}
@@ -1132,13 +1045,13 @@
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 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";
+ RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+
+ LOG(INFO) << " zeroing " << tgt.blocks() << " blocks";
+
+ 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;
- }
+ }
- RangeSet tgt = parse_range(params.tokens[params.cpos++]);
+ if (!check_lseek(params.fd, offset, SEEK_SET)) {
+ return -1;
+ }
- LOG(INFO) << " zeroing " << tgt.size << " blocks";
-
- allocate(BLOCKSIZE, params.buffer);
- memset(params.buffer.data(), 0, BLOCKSIZE);
-
- 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;
- }
- }
+ 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 = RangeSet::Parse(params.tokens[params.cpos++]);
- RangeSet tgt = parse_range(params.tokens[params.cpos++]);
+ if (params.canwrite) {
+ LOG(INFO) << " writing " << tgt.blocks() << " blocks of new data";
- if (params.canwrite) {
- LOG(INFO) << " writing " << tgt.size << " blocks of new data";
+ RangeSinkWriter writer(params.fd, tgt);
+ pthread_mutex_lock(¶ms.nti.mu);
+ params.nti.writer = &writer;
+ pthread_cond_broadcast(¶ms.nti.cv);
- 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;
- }
-
- pthread_mutex_lock(¶ms.nti.mu);
- params.nti.rss = &rss;
- pthread_cond_broadcast(¶ms.nti.cv);
-
- while (params.nti.rss) {
- pthread_cond_wait(¶ms.nti.cv, ¶ms.nti.mu);
- }
-
+ 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 << "]";
- }
+ LOG(INFO) << "patching " << blocks << " blocks to " << tgt.blocks();
+ Value patch_value(
+ VAL_BLOB, std::string(reinterpret_cast<const char*>(params.patch_start + offset), len));
- 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 << "]";
+ 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;
+ }
+ }
+
+ // 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 << "]";
}
+ }
- if (!params.freestash.empty()) {
- FreeStash(params.stashbase, params.freestash);
- params.freestash.clear();
- }
+ if (!params.freestash.empty()) {
+ FreeStash(params.stashbase, params.freestash);
+ params.freestash.clear();
+ }
- params.written += tgt.size;
+ params.written += tgt.blocks();
- return 0;
+ 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";
+ 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 (params.cpos >= params.tokens.size()) {
+ LOG(ERROR) << "missing target blocks for erase";
+ return -1;
+ }
+
+ RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]);
+
+ 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;
+ }
}
+ }
- 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;
- }
-
- RangeSet tgt = parse_range(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;
- }
- }
- }
-
- return 0;
+ return 0;
}
// Definitions for transfer list command functions
@@ -1429,10 +1324,10 @@
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 @@
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* 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());
+ 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;
+ }
+
+ 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("");
+ }
+
+ 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 = RangeSet::Parse(ranges->data);
+
+ SHA_CTX ctx;
+ SHA1_Init(&ctx);
+
+ 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 = 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);
}
+ }
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1_Final(digest, &ctx);
- 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();
-
- 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("");
- }
-
- RangeSet rs = parse_range(ranges->data);
-
- 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("");
- }
-
- 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("");
- }
-
- SHA1_Update(&ctx, buffer.data(), BLOCKSIZE);
- }
- }
- 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* 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());
+ 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;
+ }
+
+ 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("");
+ }
+
+ // 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, O_RDWR);
+
+ 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("");
+ }
+
+ fec_status status;
+ if (!fh.get_status(status)) {
+ ErrorAbort(state, kLibfecFailure, "failed to read FEC status");
+ return StringValue("");
+ }
+
+ 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.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("");
+ }
+
+ // 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.
}
-
- 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();
-
- 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...";
-
- // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read
- fec::io fh(filename->data.c_str(), O_RDWR);
-
- 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("");
- }
-
- fec_status status;
-
- if (!fh.get_status(status)) {
- ErrorAbort(state, kLibfecFailure, "failed to read FEC status");
- 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.
- }
- }
- 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 0000000..fad0380
--- /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 f91f3fc..857d7f1 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -181,8 +181,8 @@
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 @@
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 @@
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);
}