summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk49
-rw-r--r--applypatch/Android.mk12
-rw-r--r--applypatch/imgdiff.cpp1152
-rw-r--r--applypatch/include/applypatch/imgdiff_image.h247
-rw-r--r--edify/Android.mk2
-rw-r--r--error_code.h5
-rw-r--r--etc/init.rc4
-rw-r--r--install.cpp25
-rw-r--r--minadbd/Android.mk2
-rw-r--r--minui/events.cpp47
-rw-r--r--minui/graphics.cpp4
-rw-r--r--minui/include/minui/minui.h3
-rw-r--r--minui/resources.cpp2
-rw-r--r--otafault/Android.mk1
-rw-r--r--otautil/DirUtil.cpp256
-rw-r--r--otautil/DirUtil.h51
-rw-r--r--rangeset.h (renamed from updater/include/updater/rangeset.h)116
-rw-r--r--recovery-persist.cpp30
-rw-r--r--recovery.cpp155
-rw-r--r--roots.cpp419
-rw-r--r--screen_ui.cpp35
-rw-r--r--screen_ui.h9
-rw-r--r--tests/Android.mk3
-rw-r--r--tests/component/install_test.cpp71
-rw-r--r--tests/component/update_verifier_test.cpp13
-rw-r--r--tests/component/updater_test.cpp23
-rw-r--r--tests/component/verifier_test.cpp17
-rw-r--r--tests/manual/recovery_test.cpp2
-rw-r--r--tests/unit/dirutil_test.cpp64
-rw-r--r--tests/unit/rangeset_test.cpp49
-rw-r--r--tools/recovery_l10n/res/values-az/strings.xml (renamed from tools/recovery_l10n/res/values-az-rAZ/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-b+sr+Latn/strings.xml9
-rw-r--r--tools/recovery_l10n/res/values-be/strings.xml9
-rw-r--r--tools/recovery_l10n/res/values-bn/strings.xml (renamed from tools/recovery_l10n/res/values-bn-rBD/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-bs/strings.xml9
-rw-r--r--tools/recovery_l10n/res/values-et/strings.xml (renamed from tools/recovery_l10n/res/values-et-rEE/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-eu/strings.xml (renamed from tools/recovery_l10n/res/values-eu-rES/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-gl/strings.xml (renamed from tools/recovery_l10n/res/values-gl-rES/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-gu/strings.xml (renamed from tools/recovery_l10n/res/values-gu-rIN/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-hy/strings.xml (renamed from tools/recovery_l10n/res/values-hy-rAM/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-is/strings.xml (renamed from tools/recovery_l10n/res/values-is-rIS/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-ka/strings.xml (renamed from tools/recovery_l10n/res/values-ka-rGE/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-kk/strings.xml (renamed from tools/recovery_l10n/res/values-kk-rKZ/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-km/strings.xml (renamed from tools/recovery_l10n/res/values-km-rKH/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-kn/strings.xml (renamed from tools/recovery_l10n/res/values-kn-rIN/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-ky/strings.xml (renamed from tools/recovery_l10n/res/values-ky-rKG/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-lo/strings.xml (renamed from tools/recovery_l10n/res/values-lo-rLA/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-mk/strings.xml (renamed from tools/recovery_l10n/res/values-mk-rMK/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-ml/strings.xml (renamed from tools/recovery_l10n/res/values-ml-rIN/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-mn/strings.xml (renamed from tools/recovery_l10n/res/values-mn-rMN/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-mr/strings.xml (renamed from tools/recovery_l10n/res/values-mr-rIN/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-ms/strings.xml (renamed from tools/recovery_l10n/res/values-ms-rMY/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-my/strings.xml (renamed from tools/recovery_l10n/res/values-my-rMM/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-ne/strings.xml (renamed from tools/recovery_l10n/res/values-ne-rNP/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-pa/strings.xml (renamed from tools/recovery_l10n/res/values-pa-rIN/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-si/strings.xml (renamed from tools/recovery_l10n/res/values-si-rLK/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-sq/strings.xml (renamed from tools/recovery_l10n/res/values-sq-rAL/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-ta/strings.xml (renamed from tools/recovery_l10n/res/values-ta-rIN/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-te/strings.xml (renamed from tools/recovery_l10n/res/values-te-rIN/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-ur/strings.xml (renamed from tools/recovery_l10n/res/values-ur-rPK/strings.xml)0
-rw-r--r--tools/recovery_l10n/res/values-uz/strings.xml (renamed from tools/recovery_l10n/res/values-uz-rUZ/strings.xml)0
-rw-r--r--ui.cpp159
-rw-r--r--ui.h25
-rw-r--r--uncrypt/Android.mk1
-rw-r--r--uncrypt/uncrypt.cpp18
-rw-r--r--update_verifier/update_verifier.cpp174
-rw-r--r--update_verifier/update_verifier_main.cpp6
-rw-r--r--updater/blockimg.cpp177
-rw-r--r--updater/install.cpp54
-rw-r--r--updater/updater.cpp6
-rw-r--r--verifier.cpp143
-rw-r--r--wear_device.cpp23
-rw-r--r--wear_touch.cpp177
-rw-r--r--wear_touch.h58
-rw-r--r--wear_ui.cpp42
-rw-r--r--wear_ui.h18
76 files changed, 2247 insertions, 1729 deletions
diff --git a/Android.mk b/Android.mk
index c35dabc2d..aaae6a0ab 100644
--- a/Android.mk
+++ b/Android.mk
@@ -79,15 +79,16 @@ LOCAL_SRC_FILES := \
ui.cpp \
vr_ui.cpp \
wear_ui.cpp \
- wear_touch.cpp \
LOCAL_MODULE := recovery
LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_REQUIRED_MODULES := e2fsdroid_static mke2fs_static mke2fs.conf
+
ifeq ($(TARGET_USERIMAGES_USE_F2FS),true)
ifeq ($(HOST_OS),linux)
-LOCAL_REQUIRED_MODULES := mkfs.f2fs
+LOCAL_REQUIRED_MODULES += mkfs.f2fs
endif
endif
@@ -106,6 +107,36 @@ else
LOCAL_CFLAGS += -DRECOVERY_UI_MARGIN_WIDTH=0
endif
+ifneq ($(TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD),)
+LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_LOW_THRESHOLD=$(TARGET_RECOVERY_UI_TOUCH_LOW_THRESHOLD)
+else
+LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_LOW_THRESHOLD=50
+endif
+
+ifneq ($(TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD),)
+LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_HIGH_THRESHOLD=$(TARGET_RECOVERY_UI_TOUCH_HIGH_THRESHOLD)
+else
+LOCAL_CFLAGS += -DRECOVERY_UI_TOUCH_HIGH_THRESHOLD=90
+endif
+
+ifneq ($(TARGET_RECOVERY_UI_PROGRESS_BAR_BASELINE),)
+LOCAL_CFLAGS += -DRECOVERY_UI_PROGRESS_BAR_BASELINE=$(TARGET_RECOVERY_UI_PROGRESS_BAR_BASELINE)
+else
+LOCAL_CFLAGS += -DRECOVERY_UI_PROGRESS_BAR_BASELINE=259
+endif
+
+ifneq ($(TARGET_RECOVERY_UI_ANIMATION_FPS),)
+LOCAL_CFLAGS += -DRECOVERY_UI_ANIMATION_FPS=$(TARGET_RECOVERY_UI_ANIMATION_FPS)
+else
+LOCAL_CFLAGS += -DRECOVERY_UI_ANIMATION_FPS=30
+endif
+
+ifneq ($(TARGET_RECOVERY_UI_MENU_UNUSABLE_ROWS),)
+LOCAL_CFLAGS += -DRECOVERY_UI_MENU_UNUSABLE_ROWS=$(TARGET_RECOVERY_UI_MENU_UNUSABLE_ROWS)
+else
+LOCAL_CFLAGS += -DRECOVERY_UI_MENU_UNUSABLE_ROWS=9
+endif
+
ifneq ($(TARGET_RECOVERY_UI_VR_STEREO_OFFSET),)
LOCAL_CFLAGS += -DRECOVERY_UI_VR_STEREO_OFFSET=$(TARGET_RECOVERY_UI_VR_STEREO_OFFSET)
else
@@ -120,6 +151,7 @@ LOCAL_STATIC_LIBRARIES := \
libverifier \
libbatterymonitor \
libbootloader_message \
+ libfs_mgr \
libext4_utils \
libsparse \
libziparchive \
@@ -131,7 +163,6 @@ LOCAL_STATIC_LIBRARIES := \
libfusesideload \
libminui \
libpng \
- libfs_mgr \
libcrypto_utils \
libcrypto \
libvintf_recovery \
@@ -160,7 +191,7 @@ else
endif
ifeq ($(BOARD_CACHEIMAGE_PARTITION_SIZE),)
-LOCAL_REQUIRED_MODULES := recovery-persist recovery-refresh
+LOCAL_REQUIRED_MODULES += recovery-persist recovery-refresh
endif
include $(BUILD_EXECUTABLE)
@@ -203,6 +234,16 @@ LOCAL_STATIC_LIBRARIES := \
LOCAL_CFLAGS := -Werror
include $(BUILD_STATIC_LIBRARY)
+# Wear default device
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := wear_device.cpp
+
+# Should match TARGET_RECOVERY_UI_LIB in BoardConfig.mk.
+LOCAL_MODULE := librecovery_ui_wear
+
+include $(BUILD_STATIC_LIBRARY)
+
# vr headset default device
# ===============================
include $(CLEAR_VARS)
diff --git a/applypatch/Android.mk b/applypatch/Android.mk
index a7412d238..e38207c22 100644
--- a/applypatch/Android.mk
+++ b/applypatch/Android.mk
@@ -127,7 +127,8 @@ libimgdiff_src_files := imgdiff.cpp
# libbsdiff is compiled with -D_FILE_OFFSET_BITS=64.
libimgdiff_cflags := \
-Werror \
- -D_FILE_OFFSET_BITS=64
+ -D_FILE_OFFSET_BITS=64 \
+ -DZLIB_CONST
libimgdiff_static_libraries := \
libbsdiff \
@@ -150,7 +151,8 @@ LOCAL_CFLAGS := \
LOCAL_STATIC_LIBRARIES := \
$(libimgdiff_static_libraries)
LOCAL_C_INCLUDES := \
- $(LOCAL_PATH)/include
+ $(LOCAL_PATH)/include \
+ bootable/recovery
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
include $(BUILD_STATIC_LIBRARY)
@@ -165,7 +167,8 @@ LOCAL_CFLAGS := \
LOCAL_STATIC_LIBRARIES := \
$(libimgdiff_static_libraries)
LOCAL_C_INCLUDES := \
- $(LOCAL_PATH)/include
+ $(LOCAL_PATH)/include \
+ bootable/recovery
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
include $(BUILD_HOST_STATIC_LIBRARY)
@@ -179,4 +182,7 @@ LOCAL_STATIC_LIBRARIES := \
libimgdiff \
$(libimgdiff_static_libraries) \
libbz
+LOCAL_C_INCLUDES := \
+ $(LOCAL_PATH)/include \
+ bootable/recovery
include $(BUILD_HOST_EXECUTABLE)
diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp
index fc240644f..59b600713 100644
--- a/applypatch/imgdiff.cpp
+++ b/applypatch/imgdiff.cpp
@@ -140,11 +140,12 @@
#include <android-base/logging.h>
#include <android-base/memory.h>
#include <android-base/unique_fd.h>
-#include <ziparchive/zip_archive.h>
-
#include <bsdiff.h>
+#include <ziparchive/zip_archive.h>
#include <zlib.h>
+#include "applypatch/imgdiff_image.h"
+
using android::base::get_unaligned;
static constexpr auto BUFFER_SIZE = 0x8000;
@@ -161,98 +162,16 @@ static inline bool Write4(int fd, int32_t value) {
return android::base::WriteFully(fd, &value, sizeof(int32_t));
}
-class ImageChunk {
- public:
- static constexpr auto WINDOWBITS = -15; // 32kb window; negative to indicate a raw stream.
- static constexpr auto MEMLEVEL = 8; // the default value.
- static constexpr auto METHOD = Z_DEFLATED;
- static constexpr auto STRATEGY = Z_DEFAULT_STRATEGY;
-
- ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content, size_t raw_data_len)
- : type_(type),
- start_(start),
- input_file_ptr_(file_content),
- raw_data_len_(raw_data_len),
- compress_level_(6),
- source_start_(0),
- source_len_(0),
- source_uncompressed_len_(0) {
- CHECK(file_content != nullptr) << "input file container can't be nullptr";
- }
-
- int GetType() const {
- return type_;
- }
- size_t GetRawDataLength() const {
- return raw_data_len_;
- }
- const std::string& GetEntryName() const {
- return entry_name_;
- }
-
- // CHUNK_DEFLATE will return the uncompressed data for diff, while other types will simply return
- // the raw data.
- const uint8_t * DataForPatch() const;
- size_t DataLengthForPatch() const;
-
- void Dump() const {
- printf("type %d start %zu len %zu\n", type_, start_, DataLengthForPatch());
- }
-
- void SetSourceInfo(const ImageChunk& other);
- void SetEntryName(std::string entryname);
- void SetUncompressedData(std::vector<uint8_t> data);
- bool SetBonusData(const std::vector<uint8_t>& bonus_data);
-
- bool operator==(const ImageChunk& other) const;
- bool operator!=(const ImageChunk& other) const {
- return !(*this == other);
- }
-
- size_t GetHeaderSize(size_t patch_size) const;
- // Return the offset of the next patch into the patch data.
- size_t WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset);
-
- /*
- * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob
- * of uninterpreted data). The resulting patch will likely be about
- * as big as the target file, but it lets us handle the case of images
- * where some gzip chunks are reconstructible but others aren't (by
- * treating the ones that aren't as normal chunks).
- */
- void ChangeDeflateChunkToNormal();
- bool ChangeChunkToRaw(size_t patch_size);
-
- /*
- * Verify that we can reproduce exactly the same compressed data that
- * we started with. Sets the level, method, windowBits, memLevel, and
- * strategy fields in the chunk to the encoding parameters needed to
- * produce the right output.
- */
- bool ReconstructDeflateChunk();
- bool IsAdjacentNormal(const ImageChunk& other) const;
- void MergeAdjacentNormal(const ImageChunk& other);
-
- private:
- int type_; // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
- size_t start_; // offset of chunk in the original input file
- const std::vector<uint8_t>* input_file_ptr_; // ptr to the full content of original input file
- size_t raw_data_len_;
-
- // --- for CHUNK_DEFLATE chunks only: ---
- std::vector<uint8_t> uncompressed_data_;
- std::string entry_name_; // used for zip entries
-
- // deflate encoder parameters
- int compress_level_;
-
- size_t source_start_;
- size_t source_len_;
- size_t source_uncompressed_len_;
-
- const uint8_t* GetRawData() const;
- bool TryReconstruction(int level);
-};
+ImageChunk::ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content,
+ size_t raw_data_len, std::string entry_name)
+ : type_(type),
+ start_(start),
+ input_file_ptr_(file_content),
+ raw_data_len_(raw_data_len),
+ compress_level_(6),
+ entry_name_(std::move(entry_name)) {
+ CHECK(file_content != nullptr) << "input file container can't be nullptr";
+}
const uint8_t* ImageChunk::GetRawData() const {
CHECK_LE(start_ + raw_data_len_, input_file_ptr_->size());
@@ -281,20 +200,6 @@ bool ImageChunk::operator==(const ImageChunk& other) const {
memcmp(GetRawData(), other.GetRawData(), raw_data_len_) == 0);
}
-void ImageChunk::SetSourceInfo(const ImageChunk& src) {
- source_start_ = src.start_;
- if (type_ == CHUNK_NORMAL) {
- source_len_ = src.raw_data_len_;
- } else if (type_ == CHUNK_DEFLATE) {
- source_len_ = src.raw_data_len_;
- source_uncompressed_len_ = src.uncompressed_data_.size();
- }
-}
-
-void ImageChunk::SetEntryName(std::string entryname) {
- entry_name_ = std::move(entryname);
-}
-
void ImageChunk::SetUncompressedData(std::vector<uint8_t> data) {
uncompressed_data_ = std::move(data);
}
@@ -307,80 +212,13 @@ bool ImageChunk::SetBonusData(const std::vector<uint8_t>& bonus_data) {
return true;
}
-// Convert CHUNK_NORMAL & CHUNK_DEFLATE to CHUNK_RAW if the target size is
-// smaller. Also take the header size into account during size comparison.
-bool ImageChunk::ChangeChunkToRaw(size_t patch_size) {
- if (type_ == CHUNK_RAW) {
- return true;
- } else if (type_ == CHUNK_NORMAL && (raw_data_len_ <= 160 || raw_data_len_ < patch_size)) {
- type_ = CHUNK_RAW;
- return true;
- }
- return false;
-}
-
void ImageChunk::ChangeDeflateChunkToNormal() {
if (type_ != CHUNK_DEFLATE) return;
type_ = CHUNK_NORMAL;
- entry_name_.clear();
+ // No need to clear the entry name.
uncompressed_data_.clear();
}
-// Header size:
-// header_type 4 bytes
-// CHUNK_NORMAL 8*3 = 24 bytes
-// CHUNK_DEFLATE 8*5 + 4*5 = 60 bytes
-// CHUNK_RAW 4 bytes + patch_size
-size_t ImageChunk::GetHeaderSize(size_t patch_size) const {
- switch (type_) {
- case CHUNK_NORMAL:
- return 4 + 8 * 3;
- case CHUNK_DEFLATE:
- return 4 + 8 * 5 + 4 * 5;
- case CHUNK_RAW:
- return 4 + 4 + patch_size;
- default:
- CHECK(false) << "unexpected chunk type: " << type_; // Should not reach here.
- return 0;
- }
-}
-
-size_t ImageChunk::WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset) {
- Write4(fd, type_);
- switch (type_) {
- case CHUNK_NORMAL:
- printf("normal (%10zu, %10zu) %10zu\n", start_, raw_data_len_, patch.size());
- Write8(fd, static_cast<int64_t>(source_start_));
- Write8(fd, static_cast<int64_t>(source_len_));
- Write8(fd, static_cast<int64_t>(offset));
- return offset + patch.size();
- case CHUNK_DEFLATE:
- printf("deflate (%10zu, %10zu) %10zu %s\n", start_, raw_data_len_, patch.size(),
- entry_name_.c_str());
- Write8(fd, static_cast<int64_t>(source_start_));
- Write8(fd, static_cast<int64_t>(source_len_));
- Write8(fd, static_cast<int64_t>(offset));
- Write8(fd, static_cast<int64_t>(source_uncompressed_len_));
- Write8(fd, static_cast<int64_t>(uncompressed_data_.size()));
- Write4(fd, compress_level_);
- Write4(fd, METHOD);
- Write4(fd, WINDOWBITS);
- Write4(fd, MEMLEVEL);
- Write4(fd, STRATEGY);
- return offset + patch.size();
- case CHUNK_RAW:
- printf("raw (%10zu, %10zu)\n", start_, raw_data_len_);
- Write4(fd, static_cast<int32_t>(patch.size()));
- if (!android::base::WriteFully(fd, patch.data(), patch.size())) {
- CHECK(false) << "failed to write " << patch.size() <<" bytes patch";
- }
- return offset;
- default:
- CHECK(false) << "unexpected chunk type: " << type_;
- return offset;
- }
-}
-
bool ImageChunk::IsAdjacentNormal(const ImageChunk& other) const {
if (type_ != CHUNK_NORMAL || other.type_ != CHUNK_NORMAL) {
return false;
@@ -393,14 +231,61 @@ void ImageChunk::MergeAdjacentNormal(const ImageChunk& other) {
raw_data_len_ = raw_data_len_ + other.raw_data_len_;
}
+bool ImageChunk::MakePatch(const ImageChunk& tgt, const ImageChunk& src,
+ std::vector<uint8_t>* patch_data, saidx_t** bsdiff_cache) {
+#if defined(__ANDROID__)
+ char ptemp[] = "/data/local/tmp/imgdiff-patch-XXXXXX";
+#else
+ char ptemp[] = "/tmp/imgdiff-patch-XXXXXX";
+#endif
+
+ int fd = mkstemp(ptemp);
+ if (fd == -1) {
+ printf("MakePatch failed to create a temporary file: %s\n", strerror(errno));
+ return false;
+ }
+ close(fd);
+
+ int r = bsdiff::bsdiff(src.DataForPatch(), src.DataLengthForPatch(), tgt.DataForPatch(),
+ tgt.DataLengthForPatch(), ptemp, bsdiff_cache);
+ if (r != 0) {
+ printf("bsdiff() failed: %d\n", r);
+ return false;
+ }
+
+ android::base::unique_fd patch_fd(open(ptemp, O_RDONLY));
+ if (patch_fd == -1) {
+ printf("failed to open %s: %s\n", ptemp, strerror(errno));
+ return false;
+ }
+ struct stat st;
+ if (fstat(patch_fd, &st) != 0) {
+ printf("failed to stat patch file %s: %s\n", ptemp, strerror(errno));
+ return false;
+ }
+
+ size_t sz = static_cast<size_t>(st.st_size);
+
+ patch_data->resize(sz);
+ if (!android::base::ReadFully(patch_fd, patch_data->data(), sz)) {
+ printf("failed to read \"%s\" %s\n", ptemp, strerror(errno));
+ unlink(ptemp);
+ return false;
+ }
+
+ unlink(ptemp);
+
+ return true;
+}
+
bool ImageChunk::ReconstructDeflateChunk() {
if (type_ != CHUNK_DEFLATE) {
printf("attempt to reconstruct non-deflate chunk\n");
return false;
}
- // We only check two combinations of encoder parameters: level 6
- // (the default) and level 9 (the maximum).
+ // We only check two combinations of encoder parameters: level 6 (the default) and level 9
+ // (the maximum).
for (int level = 6; level <= 9; level += 3) {
if (TryReconstruction(level)) {
compress_level_ = level;
@@ -412,10 +297,9 @@ bool ImageChunk::ReconstructDeflateChunk() {
}
/*
- * Takes the uncompressed data stored in the chunk, compresses it
- * using the zlib parameters stored in the chunk, and checks that it
- * matches exactly the compressed data we started with (also stored in
- * the chunk).
+ * Takes the uncompressed data stored in the chunk, compresses it using the zlib parameters stored
+ * in the chunk, and checks that it matches exactly the compressed data we started with (also
+ * stored in the chunk).
*/
bool ImageChunk::TryReconstruction(int level) {
z_stream strm;
@@ -458,195 +342,481 @@ bool ImageChunk::TryReconstruction(int level) {
return true;
}
-// EOCD record
-// offset 0: signature 0x06054b50, 4 bytes
-// offset 4: number of this disk, 2 bytes
-// ...
-// offset 20: comment length, 2 bytes
-// offset 22: comment, n bytes
-static bool GetZipFileSize(const std::vector<uint8_t>& zip_file, size_t* input_file_size) {
- if (zip_file.size() < 22) {
- printf("file is too small to be a zip file\n");
+PatchChunk::PatchChunk(const ImageChunk& tgt, const ImageChunk& src, std::vector<uint8_t> data)
+ : type_(tgt.GetType()),
+ source_start_(src.GetStartOffset()),
+ source_len_(src.GetRawDataLength()),
+ source_uncompressed_len_(src.DataLengthForPatch()),
+ target_start_(tgt.GetStartOffset()),
+ target_len_(tgt.GetRawDataLength()),
+ target_uncompressed_len_(tgt.DataLengthForPatch()),
+ target_compress_level_(tgt.GetCompressLevel()),
+ data_(std::move(data)) {}
+
+// Construct a CHUNK_RAW patch from the target data directly.
+PatchChunk::PatchChunk(const ImageChunk& tgt)
+ : type_(CHUNK_RAW),
+ source_start_(0),
+ source_len_(0),
+ source_uncompressed_len_(0),
+ target_start_(tgt.GetStartOffset()),
+ target_len_(tgt.GetRawDataLength()),
+ target_uncompressed_len_(tgt.DataLengthForPatch()),
+ target_compress_level_(tgt.GetCompressLevel()),
+ data_(tgt.DataForPatch(), tgt.DataForPatch() + tgt.DataLengthForPatch()) {}
+
+// Return true if raw data is smaller than the patch size.
+bool PatchChunk::RawDataIsSmaller(const ImageChunk& tgt, size_t patch_size) {
+ size_t target_len = tgt.GetRawDataLength();
+ return (tgt.GetType() == CHUNK_NORMAL && (target_len <= 160 || target_len < patch_size));
+}
+
+// Header size:
+// header_type 4 bytes
+// CHUNK_NORMAL 8*3 = 24 bytes
+// CHUNK_DEFLATE 8*5 + 4*5 = 60 bytes
+// CHUNK_RAW 4 bytes + patch_size
+size_t PatchChunk::GetHeaderSize() const {
+ switch (type_) {
+ case CHUNK_NORMAL:
+ return 4 + 8 * 3;
+ case CHUNK_DEFLATE:
+ return 4 + 8 * 5 + 4 * 5;
+ case CHUNK_RAW:
+ return 4 + 4 + data_.size();
+ default:
+ CHECK(false) << "unexpected chunk type: " << type_; // Should not reach here.
+ return 0;
+ }
+}
+
+// Return the offset of the next patch into the patch data.
+size_t PatchChunk::WriteHeaderToFd(int fd, size_t offset) const {
+ Write4(fd, type_);
+ switch (type_) {
+ case CHUNK_NORMAL:
+ printf("normal (%10zu, %10zu) %10zu\n", target_start_, target_len_, data_.size());
+ Write8(fd, static_cast<int64_t>(source_start_));
+ Write8(fd, static_cast<int64_t>(source_len_));
+ Write8(fd, static_cast<int64_t>(offset));
+ return offset + data_.size();
+ case CHUNK_DEFLATE:
+ printf("deflate (%10zu, %10zu) %10zu\n", target_start_, target_len_, data_.size());
+ Write8(fd, static_cast<int64_t>(source_start_));
+ Write8(fd, static_cast<int64_t>(source_len_));
+ Write8(fd, static_cast<int64_t>(offset));
+ Write8(fd, static_cast<int64_t>(source_uncompressed_len_));
+ Write8(fd, static_cast<int64_t>(target_uncompressed_len_));
+ Write4(fd, target_compress_level_);
+ Write4(fd, ImageChunk::METHOD);
+ Write4(fd, ImageChunk::WINDOWBITS);
+ Write4(fd, ImageChunk::MEMLEVEL);
+ Write4(fd, ImageChunk::STRATEGY);
+ return offset + data_.size();
+ case CHUNK_RAW:
+ printf("raw (%10zu, %10zu)\n", target_start_, target_len_);
+ Write4(fd, static_cast<int32_t>(data_.size()));
+ if (!android::base::WriteFully(fd, data_.data(), data_.size())) {
+ CHECK(false) << "failed to write " << data_.size() << " bytes patch";
+ }
+ return offset;
+ default:
+ CHECK(false) << "unexpected chunk type: " << type_;
+ return offset;
+ }
+}
+
+// Write the contents of |patch_chunks| to |patch_fd|.
+bool PatchChunk::WritePatchDataToFd(const std::vector<PatchChunk>& patch_chunks, int patch_fd) {
+ // Figure out how big the imgdiff file header is going to be, so that we can correctly compute
+ // the offset of each bsdiff patch within the file.
+ size_t total_header_size = 12;
+ for (const auto& patch : patch_chunks) {
+ total_header_size += patch.GetHeaderSize();
+ }
+
+ size_t offset = total_header_size;
+
+ // Write out the headers.
+ if (!android::base::WriteStringToFd("IMGDIFF2", patch_fd)) {
+ printf("failed to write \"IMGDIFF2\": %s\n", strerror(errno));
return false;
}
- // Look for End of central directory record of the zip file, and calculate the actual
- // zip_file size.
- for (int i = zip_file.size() - 22; i >= 0; i--) {
- if (zip_file[i] == 0x50) {
- if (get_unaligned<uint32_t>(&zip_file[i]) == 0x06054b50) {
- // double-check: this archive consists of a single "disk".
- CHECK_EQ(get_unaligned<uint16_t>(&zip_file[i + 4]), 0);
+ Write4(patch_fd, static_cast<int32_t>(patch_chunks.size()));
+ for (size_t i = 0; i < patch_chunks.size(); ++i) {
+ printf("chunk %zu: ", i);
+ offset = patch_chunks[i].WriteHeaderToFd(patch_fd, offset);
+ }
- uint16_t comment_length = get_unaligned<uint16_t>(&zip_file[i + 20]);
- size_t file_size = i + 22 + comment_length;
- CHECK_LE(file_size, zip_file.size());
- *input_file_size = file_size;
- return true;
- }
+ // Append each chunk's bsdiff patch, in order.
+ for (const auto& patch : patch_chunks) {
+ if (patch.type_ == CHUNK_RAW) {
+ continue;
+ }
+ if (!android::base::WriteFully(patch_fd, patch.data_.data(), patch.data_.size())) {
+ printf("failed to write %zu bytes patch to patch_fd\n", patch.data_.size());
+ return false;
}
}
- // EOCD not found, this file is likely not a valid zip file.
- return false;
+ return true;
+}
+
+ImageChunk& Image::operator[](size_t i) {
+ CHECK_LT(i, chunks_.size());
+ return chunks_[i];
+}
+
+const ImageChunk& Image::operator[](size_t i) const {
+ CHECK_LT(i, chunks_.size());
+ return chunks_[i];
}
-static bool ReadZip(const char* filename, std::vector<ImageChunk>* chunks,
- std::vector<uint8_t>* zip_file, bool include_pseudo_chunk) {
- CHECK(chunks != nullptr && zip_file != nullptr);
+void Image::MergeAdjacentNormalChunks() {
+ size_t merged_last = 0, cur = 0;
+ while (cur < chunks_.size()) {
+ // Look for normal chunks adjacent to the current one. If such chunk exists, extend the
+ // length of the current normal chunk.
+ size_t to_check = cur + 1;
+ while (to_check < chunks_.size() && chunks_[cur].IsAdjacentNormal(chunks_[to_check])) {
+ chunks_[cur].MergeAdjacentNormal(chunks_[to_check]);
+ to_check++;
+ }
+
+ if (merged_last != cur) {
+ chunks_[merged_last] = std::move(chunks_[cur]);
+ }
+ merged_last++;
+ cur = to_check;
+ }
+ if (merged_last < chunks_.size()) {
+ chunks_.erase(chunks_.begin() + merged_last, chunks_.end());
+ }
+}
- android::base::unique_fd fd(open(filename, O_RDONLY));
+void Image::DumpChunks() const {
+ std::string type = is_source_ ? "source" : "target";
+ printf("Dumping chunks for %s\n", type.c_str());
+ for (size_t i = 0; i < chunks_.size(); ++i) {
+ printf("chunk %zu: ", i);
+ chunks_[i].Dump();
+ }
+}
+
+bool Image::ReadFile(const std::string& filename, std::vector<uint8_t>* file_content) {
+ CHECK(file_content != nullptr);
+
+ android::base::unique_fd fd(open(filename.c_str(), O_RDONLY));
if (fd == -1) {
- printf("failed to open \"%s\" %s\n", filename, strerror(errno));
+ printf("failed to open \"%s\" %s\n", filename.c_str(), strerror(errno));
return false;
}
struct stat st;
if (fstat(fd, &st) != 0) {
- printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
+ printf("failed to stat \"%s\": %s\n", filename.c_str(), strerror(errno));
return false;
}
size_t sz = static_cast<size_t>(st.st_size);
- zip_file->resize(sz);
- if (!android::base::ReadFully(fd, zip_file->data(), sz)) {
- printf("failed to read \"%s\" %s\n", filename, strerror(errno));
+ file_content->resize(sz);
+ if (!android::base::ReadFully(fd, file_content->data(), sz)) {
+ printf("failed to read \"%s\" %s\n", filename.c_str(), strerror(errno));
return false;
}
fd.reset();
- // Trim the trailing zeros before we pass the file to ziparchive handler.
+ return true;
+}
+
+bool ZipModeImage::Initialize(const std::string& filename) {
+ if (!ReadFile(filename, &file_content_)) {
+ return false;
+ }
+
+ // Omit the trailing zeros before we pass the file to ziparchive handler.
size_t zipfile_size;
- if (!GetZipFileSize(*zip_file, &zipfile_size)) {
- printf("failed to parse the actual size of %s\n", filename);
+ if (!GetZipFileSize(&zipfile_size)) {
+ printf("failed to parse the actual size of %s\n", filename.c_str());
return false;
}
ZipArchiveHandle handle;
- int err = OpenArchiveFromMemory(zip_file->data(), zipfile_size, filename, &handle);
+ int err = OpenArchiveFromMemory(const_cast<uint8_t*>(file_content_.data()), zipfile_size,
+ filename.c_str(), &handle);
if (err != 0) {
- printf("failed to open zip file %s: %s\n", filename, ErrorCodeString(err));
+ printf("failed to open zip file %s: %s\n", filename.c_str(), ErrorCodeString(err));
CloseArchive(handle);
return false;
}
- // Create a list of deflated zip entries, sorted by offset.
- std::vector<std::pair<std::string, ZipEntry>> temp_entries;
+ if (!InitializeChunks(filename, handle)) {
+ CloseArchive(handle);
+ return false;
+ }
+
+ CloseArchive(handle);
+ return true;
+}
+
+// Iterate the zip entries and compose the image chunks accordingly.
+bool ZipModeImage::InitializeChunks(const std::string& filename, ZipArchiveHandle handle) {
void* cookie;
int ret = StartIteration(handle, &cookie, nullptr, nullptr);
if (ret != 0) {
- printf("failed to iterate over entries in %s: %s\n", filename, ErrorCodeString(ret));
- CloseArchive(handle);
+ printf("failed to iterate over entries in %s: %s\n", filename.c_str(), ErrorCodeString(ret));
return false;
}
+ // Create a list of deflated zip entries, sorted by offset.
+ std::vector<std::pair<std::string, ZipEntry>> temp_entries;
ZipString name;
ZipEntry entry;
while ((ret = Next(cookie, &entry, &name)) == 0) {
if (entry.method == kCompressDeflated) {
- std::string entryname(name.name, name.name + name.name_length);
- temp_entries.push_back(std::make_pair(entryname, entry));
+ std::string entry_name(name.name, name.name + name.name_length);
+ temp_entries.emplace_back(entry_name, entry);
}
}
if (ret != -1) {
printf("Error while iterating over zip entries: %s\n", ErrorCodeString(ret));
- CloseArchive(handle);
return false;
}
std::sort(temp_entries.begin(), temp_entries.end(),
- [](auto& entry1, auto& entry2) {
- return entry1.second.offset < entry2.second.offset;
- });
+ [](auto& entry1, auto& entry2) { return entry1.second.offset < entry2.second.offset; });
EndIteration(cookie);
- if (include_pseudo_chunk) {
- chunks->emplace_back(CHUNK_NORMAL, 0, zip_file, zip_file->size());
+ // For source chunks, we don't need to compose chunks for the metadata.
+ if (is_source_) {
+ for (auto& entry : temp_entries) {
+ if (!AddZipEntryToChunks(handle, entry.first, &entry.second)) {
+ printf("Failed to add %s to source chunks\n", entry.first.c_str());
+ return false;
+ }
+ }
+ return true;
}
+ // For target chunks, add the deflate entries as CHUNK_DEFLATE and the contents between two
+ // deflate entries as CHUNK_NORMAL.
size_t pos = 0;
size_t nextentry = 0;
- while (pos < zip_file->size()) {
+ while (pos < file_content_.size()) {
if (nextentry < temp_entries.size() &&
static_cast<off64_t>(pos) == temp_entries[nextentry].second.offset) {
- // compose the next deflate chunk.
- std::string entryname = temp_entries[nextentry].first;
- size_t uncompressed_len = temp_entries[nextentry].second.uncompressed_length;
- std::vector<uint8_t> uncompressed_data(uncompressed_len);
- if ((ret = ExtractToMemory(handle, &temp_entries[nextentry].second, uncompressed_data.data(),
- uncompressed_len)) != 0) {
- printf("failed to extract %s with size %zu: %s\n", entryname.c_str(), uncompressed_len,
- ErrorCodeString(ret));
- CloseArchive(handle);
+ // Add the next zip entry.
+ std::string entry_name = temp_entries[nextentry].first;
+ if (!AddZipEntryToChunks(handle, entry_name, &temp_entries[nextentry].second)) {
+ printf("Failed to add %s to target chunks\n", entry_name.c_str());
return false;
}
- size_t compressed_len = temp_entries[nextentry].second.compressed_length;
- ImageChunk curr(CHUNK_DEFLATE, pos, zip_file, compressed_len);
- curr.SetEntryName(std::move(entryname));
- curr.SetUncompressedData(std::move(uncompressed_data));
- chunks->push_back(curr);
-
- pos += compressed_len;
+ pos += temp_entries[nextentry].second.compressed_length;
++nextentry;
continue;
}
- // Use a normal chunk to take all the data up to the start of the next deflate section.
+ // Use a normal chunk to take all the data up to the start of the next entry.
size_t raw_data_len;
if (nextentry < temp_entries.size()) {
raw_data_len = temp_entries[nextentry].second.offset - pos;
} else {
- raw_data_len = zip_file->size() - pos;
+ raw_data_len = file_content_.size() - pos;
}
- chunks->emplace_back(CHUNK_NORMAL, pos, zip_file, raw_data_len);
+ chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, raw_data_len);
pos += raw_data_len;
}
- CloseArchive(handle);
return true;
}
-// Read the given file and break it up into chunks, and putting the data in to a vector.
-static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks,
- std::vector<uint8_t>* img) {
- CHECK(chunks != nullptr && img != nullptr);
+bool ZipModeImage::AddZipEntryToChunks(ZipArchiveHandle handle, const std::string& entry_name,
+ ZipEntry* entry) {
+ size_t compressed_len = entry->compressed_length;
+ if (entry->method == kCompressDeflated) {
+ size_t uncompressed_len = entry->uncompressed_length;
+ std::vector<uint8_t> uncompressed_data(uncompressed_len);
+ int ret = ExtractToMemory(handle, entry, uncompressed_data.data(), uncompressed_len);
+ if (ret != 0) {
+ printf("failed to extract %s with size %zu: %s\n", entry_name.c_str(), uncompressed_len,
+ ErrorCodeString(ret));
+ return false;
+ }
+ ImageChunk curr(CHUNK_DEFLATE, entry->offset, &file_content_, compressed_len, entry_name);
+ curr.SetUncompressedData(std::move(uncompressed_data));
+ chunks_.push_back(std::move(curr));
+ } else {
+ chunks_.emplace_back(CHUNK_NORMAL, entry->offset, &file_content_, compressed_len, entry_name);
+ }
+
+ return true;
+}
- android::base::unique_fd fd(open(filename, O_RDONLY));
- if (fd == -1) {
- printf("failed to open \"%s\" %s\n", filename, strerror(errno));
+// EOCD record
+// offset 0: signature 0x06054b50, 4 bytes
+// offset 4: number of this disk, 2 bytes
+// ...
+// offset 20: comment length, 2 bytes
+// offset 22: comment, n bytes
+bool ZipModeImage::GetZipFileSize(size_t* input_file_size) {
+ if (file_content_.size() < 22) {
+ printf("file is too small to be a zip file\n");
return false;
}
- struct stat st;
- if (fstat(fd, &st) != 0) {
- printf("failed to stat \"%s\": %s\n", filename, strerror(errno));
+
+ // Look for End of central directory record of the zip file, and calculate the actual
+ // zip_file size.
+ for (int i = file_content_.size() - 22; i >= 0; i--) {
+ if (file_content_[i] == 0x50) {
+ if (get_unaligned<uint32_t>(&file_content_[i]) == 0x06054b50) {
+ // double-check: this archive consists of a single "disk".
+ CHECK_EQ(get_unaligned<uint16_t>(&file_content_[i + 4]), 0);
+
+ uint16_t comment_length = get_unaligned<uint16_t>(&file_content_[i + 20]);
+ size_t file_size = i + 22 + comment_length;
+ CHECK_LE(file_size, file_content_.size());
+ *input_file_size = file_size;
+ return true;
+ }
+ }
+ }
+
+ // EOCD not found, this file is likely not a valid zip file.
+ return false;
+}
+
+ImageChunk ZipModeImage::PseudoSource() const {
+ CHECK(is_source_);
+ return ImageChunk(CHUNK_NORMAL, 0, &file_content_, file_content_.size());
+}
+
+const ImageChunk* ZipModeImage::FindChunkByName(const std::string& name, bool find_normal) const {
+ if (name.empty()) {
+ return nullptr;
+ }
+ for (auto& chunk : chunks_) {
+ if ((chunk.GetType() == CHUNK_DEFLATE || find_normal) && chunk.GetEntryName() == name) {
+ return &chunk;
+ }
+ }
+ return nullptr;
+}
+
+ImageChunk* ZipModeImage::FindChunkByName(const std::string& name, bool find_normal) {
+ return const_cast<ImageChunk*>(
+ static_cast<const ZipModeImage*>(this)->FindChunkByName(name, find_normal));
+}
+
+bool ZipModeImage::CheckAndProcessChunks(ZipModeImage* tgt_image, ZipModeImage* src_image) {
+ for (auto& tgt_chunk : *tgt_image) {
+ if (tgt_chunk.GetType() != CHUNK_DEFLATE) {
+ continue;
+ }
+
+ ImageChunk* src_chunk = src_image->FindChunkByName(tgt_chunk.GetEntryName());
+ if (src_chunk == nullptr) {
+ tgt_chunk.ChangeDeflateChunkToNormal();
+ } else if (tgt_chunk == *src_chunk) {
+ // If two deflate chunks are identical (eg, the kernel has not changed between two builds),
+ // treat them as normal chunks. This makes applypatch much faster -- it can apply a trivial
+ // patch to the compressed data, rather than uncompressing and recompressing to apply the
+ // trivial patch to the uncompressed data.
+ tgt_chunk.ChangeDeflateChunkToNormal();
+ src_chunk->ChangeDeflateChunkToNormal();
+ } else if (!tgt_chunk.ReconstructDeflateChunk()) {
+ // We cannot recompress the data and get exactly the same bits as are in the input target
+ // image. Treat the chunk as a normal non-deflated chunk.
+ printf("failed to reconstruct target deflate chunk [%s]; treating as normal\n",
+ tgt_chunk.GetEntryName().c_str());
+
+ tgt_chunk.ChangeDeflateChunkToNormal();
+ src_chunk->ChangeDeflateChunkToNormal();
+ }
+ }
+
+ // For zips, we only need merge normal chunks for the target: deflated chunks are matched via
+ // filename, and normal chunks are patched using the entire source file as the source.
+ tgt_image->MergeAdjacentNormalChunks();
+ tgt_image->DumpChunks();
+
+ return true;
+}
+
+bool ZipModeImage::GeneratePatches(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
+ const std::string& patch_name) {
+ printf("Construct patches for %zu chunks...\n", tgt_image.NumOfChunks());
+ std::vector<PatchChunk> patch_chunks;
+ patch_chunks.reserve(tgt_image.NumOfChunks());
+
+ saidx_t* bsdiff_cache = nullptr;
+ for (size_t i = 0; i < tgt_image.NumOfChunks(); i++) {
+ const auto& tgt_chunk = tgt_image[i];
+
+ if (PatchChunk::RawDataIsSmaller(tgt_chunk, 0)) {
+ patch_chunks.emplace_back(tgt_chunk);
+ continue;
+ }
+
+ const ImageChunk* src_chunk = (tgt_chunk.GetType() != CHUNK_DEFLATE)
+ ? nullptr
+ : src_image.FindChunkByName(tgt_chunk.GetEntryName());
+
+ const auto& src_ref = (src_chunk == nullptr) ? src_image.PseudoSource() : *src_chunk;
+ saidx_t** bsdiff_cache_ptr = (src_chunk == nullptr) ? &bsdiff_cache : nullptr;
+
+ std::vector<uint8_t> patch_data;
+ if (!ImageChunk::MakePatch(tgt_chunk, src_ref, &patch_data, bsdiff_cache_ptr)) {
+ printf("Failed to generate patch, name: %s\n", tgt_chunk.GetEntryName().c_str());
+ return false;
+ }
+
+ printf("patch %3zu is %zu bytes (of %zu)\n", i, patch_data.size(),
+ tgt_chunk.GetRawDataLength());
+
+ if (PatchChunk::RawDataIsSmaller(tgt_chunk, patch_data.size())) {
+ patch_chunks.emplace_back(tgt_chunk);
+ } else {
+ patch_chunks.emplace_back(tgt_chunk, src_ref, std::move(patch_data));
+ }
+ }
+ free(bsdiff_cache);
+
+ CHECK_EQ(tgt_image.NumOfChunks(), patch_chunks.size());
+
+ android::base::unique_fd patch_fd(
+ open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+ if (patch_fd == -1) {
+ printf("failed to open \"%s\": %s\n", patch_name.c_str(), strerror(errno));
return false;
}
- size_t sz = static_cast<size_t>(st.st_size);
- img->resize(sz);
- if (!android::base::ReadFully(fd, img->data(), sz)) {
- printf("failed to read \"%s\" %s\n", filename, strerror(errno));
+ return PatchChunk::WritePatchDataToFd(patch_chunks, patch_fd);
+}
+
+bool ImageModeImage::Initialize(const std::string& filename) {
+ if (!ReadFile(filename, &file_content_)) {
return false;
}
+ size_t sz = file_content_.size();
size_t pos = 0;
-
while (pos < sz) {
// 0x00 no header flags, 0x08 deflate compression, 0x1f8b gzip magic number
- if (sz - pos >= 4 && get_unaligned<uint32_t>(img->data() + pos) == 0x00088b1f) {
+ if (sz - pos >= 4 && get_unaligned<uint32_t>(file_content_.data() + pos) == 0x00088b1f) {
// 'pos' is the offset of the start of a gzip chunk.
size_t chunk_offset = pos;
// The remaining data is too small to be a gzip chunk; treat them as a normal chunk.
if (sz - pos < GZIP_HEADER_LEN + GZIP_FOOTER_LEN) {
- chunks->emplace_back(CHUNK_NORMAL, pos, img, sz - pos);
+ chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, sz - pos);
break;
}
// We need three chunks for the deflated image in total, one normal chunk for the header,
// one deflated chunk for the body, and another normal chunk for the footer.
- chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_HEADER_LEN);
+ chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, GZIP_HEADER_LEN);
pos += GZIP_HEADER_LEN;
// We must decompress this chunk in order to discover where it ends, and so we can update
@@ -657,7 +827,7 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks,
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = sz - pos;
- strm.next_in = img->data() + pos;
+ strm.next_in = file_content_.data() + pos;
// -15 means we are decoding a 'raw' deflate stream; zlib will
// not expect zlib headers.
@@ -700,22 +870,22 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks,
printf("Warning: invalid footer position; treating as a nomal chunk\n");
continue;
}
- size_t footer_size = get_unaligned<uint32_t>(img->data() + footer_index);
+ size_t footer_size = get_unaligned<uint32_t>(file_content_.data() + footer_index);
if (footer_size != uncompressed_len) {
printf("Warning: footer size %zu != decompressed size %zu; treating as a nomal chunk\n",
footer_size, uncompressed_len);
continue;
}
- ImageChunk body(CHUNK_DEFLATE, pos, img, raw_data_len);
+ ImageChunk body(CHUNK_DEFLATE, pos, &file_content_, raw_data_len);
uncompressed_data.resize(uncompressed_len);
body.SetUncompressedData(std::move(uncompressed_data));
- chunks->push_back(body);
+ chunks_.push_back(std::move(body));
pos += raw_data_len;
// create a normal chunk for the footer
- chunks->emplace_back(CHUNK_NORMAL, pos, img, GZIP_FOOTER_LEN);
+ chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, GZIP_FOOTER_LEN);
pos += GZIP_FOOTER_LEN;
} else {
@@ -726,12 +896,12 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks,
size_t data_len = 0;
while (data_len + pos < sz) {
if (data_len + pos + 4 <= sz &&
- get_unaligned<uint32_t>(img->data() + pos + data_len) == 0x00088b1f) {
+ get_unaligned<uint32_t>(file_content_.data() + pos + data_len) == 0x00088b1f) {
break;
}
data_len++;
}
- chunks->emplace_back(CHUNK_NORMAL, pos, img, data_len);
+ chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, data_len);
pos += data_len;
}
@@ -740,346 +910,202 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks,
return true;
}
-/*
- * Given source and target chunks, compute a bsdiff patch between them.
- * Store the result in the patch_data.
- * |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk
- * is used repeatedly, pass nullptr if not needed.
- */
-static bool MakePatch(const ImageChunk* src, ImageChunk* tgt, std::vector<uint8_t>* patch_data,
- saidx_t** bsdiff_cache) {
- if (tgt->ChangeChunkToRaw(0)) {
- size_t patch_size = tgt->DataLengthForPatch();
- patch_data->resize(patch_size);
- std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin());
- return true;
- }
-
-#if defined(__ANDROID__)
- char ptemp[] = "/data/local/tmp/imgdiff-patch-XXXXXX";
-#else
- char ptemp[] = "/tmp/imgdiff-patch-XXXXXX";
-#endif
-
- int fd = mkstemp(ptemp);
- if (fd == -1) {
- printf("MakePatch failed to create a temporary file: %s\n", strerror(errno));
+bool ImageModeImage::SetBonusData(const std::vector<uint8_t>& bonus_data) {
+ CHECK(is_source_);
+ if (chunks_.size() < 2 || !chunks_[1].SetBonusData(bonus_data)) {
+ printf("Failed to set bonus data\n");
+ DumpChunks();
return false;
}
- close(fd);
- int r = bsdiff::bsdiff(src->DataForPatch(), src->DataLengthForPatch(), tgt->DataForPatch(),
- tgt->DataLengthForPatch(), ptemp, bsdiff_cache);
- if (r != 0) {
- printf("bsdiff() failed: %d\n", r);
- return false;
- }
+ printf(" using %zu bytes of bonus data\n", bonus_data.size());
+ return true;
+}
- android::base::unique_fd patch_fd(open(ptemp, O_RDONLY));
- if (patch_fd == -1) {
- printf("failed to open %s: %s\n", ptemp, strerror(errno));
+// In Image Mode, verify that the source and target images have the same chunk structure (ie, the
+// same sequence of deflate and normal chunks).
+bool ImageModeImage::CheckAndProcessChunks(ImageModeImage* tgt_image, ImageModeImage* src_image) {
+ // In image mode, merge the gzip header and footer in with any adjacent normal chunks.
+ tgt_image->MergeAdjacentNormalChunks();
+ src_image->MergeAdjacentNormalChunks();
+
+ if (tgt_image->NumOfChunks() != src_image->NumOfChunks()) {
+ printf("source and target don't have same number of chunks!\n");
+ tgt_image->DumpChunks();
+ src_image->DumpChunks();
return false;
}
- struct stat st;
- if (fstat(patch_fd, &st) != 0) {
- printf("failed to stat patch file %s: %s\n", ptemp, strerror(errno));
- return false;
+ for (size_t i = 0; i < tgt_image->NumOfChunks(); ++i) {
+ if ((*tgt_image)[i].GetType() != (*src_image)[i].GetType()) {
+ printf("source and target don't have same chunk structure! (chunk %zu)\n", i);
+ tgt_image->DumpChunks();
+ src_image->DumpChunks();
+ return false;
+ }
}
- size_t sz = static_cast<size_t>(st.st_size);
- // Change the chunk type to raw if the patch takes less space that way.
- if (tgt->ChangeChunkToRaw(sz)) {
- unlink(ptemp);
- size_t patch_size = tgt->DataLengthForPatch();
- patch_data->resize(patch_size);
- std::copy(tgt->DataForPatch(), tgt->DataForPatch() + patch_size, patch_data->begin());
- return true;
+ for (size_t i = 0; i < tgt_image->NumOfChunks(); ++i) {
+ auto& tgt_chunk = (*tgt_image)[i];
+ auto& src_chunk = (*src_image)[i];
+ if (tgt_chunk.GetType() != CHUNK_DEFLATE) {
+ continue;
+ }
+
+ // If two deflate chunks are identical treat them as normal chunks.
+ if (tgt_chunk == src_chunk) {
+ tgt_chunk.ChangeDeflateChunkToNormal();
+ src_chunk.ChangeDeflateChunkToNormal();
+ } else if (!tgt_chunk.ReconstructDeflateChunk()) {
+ // We cannot recompress the data and get exactly the same bits as are in the input target
+ // image, fall back to normal
+ printf("failed to reconstruct target deflate chunk %zu [%s]; treating as normal\n", i,
+ tgt_chunk.GetEntryName().c_str());
+ tgt_chunk.ChangeDeflateChunkToNormal();
+ src_chunk.ChangeDeflateChunkToNormal();
+ }
}
- patch_data->resize(sz);
- if (!android::base::ReadFully(patch_fd, patch_data->data(), sz)) {
- printf("failed to read \"%s\" %s\n", ptemp, strerror(errno));
+
+ // For images, we need to maintain the parallel structure of the chunk lists, so do the merging
+ // in both the source and target lists.
+ tgt_image->MergeAdjacentNormalChunks();
+ src_image->MergeAdjacentNormalChunks();
+ if (tgt_image->NumOfChunks() != src_image->NumOfChunks()) {
+ // This shouldn't happen.
+ printf("merging normal chunks went awry\n");
return false;
}
- unlink(ptemp);
- tgt->SetSourceInfo(*src);
-
return true;
}
-/*
- * Look for runs of adjacent normal chunks and compress them down into
- * a single chunk. (Such runs can be produced when deflate chunks are
- * changed to normal chunks.)
- */
-static void MergeAdjacentNormalChunks(std::vector<ImageChunk>* chunks) {
- size_t merged_last = 0, cur = 0;
- while (cur < chunks->size()) {
- // Look for normal chunks adjacent to the current one. If such chunk exists, extend the
- // length of the current normal chunk.
- size_t to_check = cur + 1;
- while (to_check < chunks->size() && chunks->at(cur).IsAdjacentNormal(chunks->at(to_check))) {
- chunks->at(cur).MergeAdjacentNormal(chunks->at(to_check));
- to_check++;
+// In image mode, generate patches against the given source chunks and bonus_data; write the
+// result to |patch_name|.
+bool ImageModeImage::GeneratePatches(const ImageModeImage& tgt_image,
+ const ImageModeImage& src_image,
+ const std::string& patch_name) {
+ printf("Construct patches for %zu chunks...\n", tgt_image.NumOfChunks());
+ std::vector<PatchChunk> patch_chunks;
+ patch_chunks.reserve(tgt_image.NumOfChunks());
+
+ for (size_t i = 0; i < tgt_image.NumOfChunks(); i++) {
+ const auto& tgt_chunk = tgt_image[i];
+ const auto& src_chunk = src_image[i];
+
+ if (PatchChunk::RawDataIsSmaller(tgt_chunk, 0)) {
+ patch_chunks.emplace_back(tgt_chunk);
+ continue;
}
- if (merged_last != cur) {
- chunks->at(merged_last) = std::move(chunks->at(cur));
+ std::vector<uint8_t> patch_data;
+ if (!ImageChunk::MakePatch(tgt_chunk, src_chunk, &patch_data, nullptr)) {
+ printf("Failed to generate patch for target chunk %zu: ", i);
+ return false;
}
- merged_last++;
- cur = to_check;
- }
- if (merged_last < chunks->size()) {
- chunks->erase(chunks->begin() + merged_last, chunks->end());
- }
-}
+ printf("patch %3zu is %zu bytes (of %zu)\n", i, patch_data.size(),
+ tgt_chunk.GetRawDataLength());
-static ImageChunk* FindChunkByName(const std::string& name, std::vector<ImageChunk>& chunks) {
- for (size_t i = 0; i < chunks.size(); ++i) {
- if (chunks[i].GetType() == CHUNK_DEFLATE && chunks[i].GetEntryName() == name) {
- return &chunks[i];
+ if (PatchChunk::RawDataIsSmaller(tgt_chunk, patch_data.size())) {
+ patch_chunks.emplace_back(tgt_chunk);
+ } else {
+ patch_chunks.emplace_back(tgt_chunk, src_chunk, std::move(patch_data));
}
}
- return nullptr;
-}
-static void DumpChunks(const std::vector<ImageChunk>& chunks) {
- for (size_t i = 0; i < chunks.size(); ++i) {
- printf("chunk %zu: ", i);
- chunks[i].Dump();
+ CHECK_EQ(tgt_image.NumOfChunks(), patch_chunks.size());
+
+ android::base::unique_fd patch_fd(
+ open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
+ if (patch_fd == -1) {
+ printf("failed to open \"%s\": %s\n", patch_name.c_str(), strerror(errno));
+ return false;
}
+
+ return PatchChunk::WritePatchDataToFd(patch_chunks, patch_fd);
}
int imgdiff(int argc, const char** argv) {
bool zip_mode = false;
+ std::vector<uint8_t> bonus_data;
- if (argc >= 2 && strcmp(argv[1], "-z") == 0) {
- zip_mode = true;
- --argc;
- ++argv;
- }
+ int opt;
+ optind = 1; // Reset the getopt state so that we can call it multiple times for test.
- std::vector<uint8_t> bonus_data;
- if (argc >= 3 && strcmp(argv[1], "-b") == 0) {
- android::base::unique_fd fd(open(argv[2], O_RDONLY));
- if (fd == -1) {
- printf("failed to open bonus file %s: %s\n", argv[2], strerror(errno));
- return 1;
- }
- struct stat st;
- if (fstat(fd, &st) != 0) {
- printf("failed to stat bonus file %s: %s\n", argv[2], strerror(errno));
- return 1;
- }
+ while ((opt = getopt(argc, const_cast<char**>(argv), "zb:")) != -1) {
+ switch (opt) {
+ case 'z':
+ zip_mode = true;
+ break;
+ case 'b': {
+ android::base::unique_fd fd(open(optarg, O_RDONLY));
+ if (fd == -1) {
+ printf("failed to open bonus file %s: %s\n", optarg, strerror(errno));
+ return 1;
+ }
+ struct stat st;
+ if (fstat(fd, &st) != 0) {
+ printf("failed to stat bonus file %s: %s\n", optarg, strerror(errno));
+ return 1;
+ }
- size_t bonus_size = st.st_size;
- bonus_data.resize(bonus_size);
- if (!android::base::ReadFully(fd, bonus_data.data(), bonus_size)) {
- printf("failed to read bonus file %s: %s\n", argv[2], strerror(errno));
- return 1;
+ size_t bonus_size = st.st_size;
+ bonus_data.resize(bonus_size);
+ if (!android::base::ReadFully(fd, bonus_data.data(), bonus_size)) {
+ printf("failed to read bonus file %s: %s\n", optarg, strerror(errno));
+ return 1;
+ }
+ break;
+ }
+ default:
+ printf("unexpected opt: %s\n", optarg);
+ return 2;
}
-
- argc -= 2;
- argv += 2;
}
- if (argc != 4) {
- printf("usage: %s [-z] [-b <bonus-file>] <src-img> <tgt-img> <patch-file>\n",
- argv[0]);
+ if (argc - optind != 3) {
+ printf("usage: %s [-z] [-b <bonus-file>] <src-img> <tgt-img> <patch-file>\n", argv[0]);
return 2;
}
- std::vector<ImageChunk> src_chunks;
- std::vector<ImageChunk> tgt_chunks;
- std::vector<uint8_t> src_file;
- std::vector<uint8_t> tgt_file;
-
if (zip_mode) {
- if (!ReadZip(argv[1], &src_chunks, &src_file, true)) {
- printf("failed to break apart source zip file\n");
+ ZipModeImage src_image(true);
+ ZipModeImage tgt_image(false);
+
+ if (!src_image.Initialize(argv[optind])) {
return 1;
}
- if (!ReadZip(argv[2], &tgt_chunks, &tgt_file, false)) {
- printf("failed to break apart target zip file\n");
+ if (!tgt_image.Initialize(argv[optind + 1])) {
return 1;
}
- } else {
- if (!ReadImage(argv[1], &src_chunks, &src_file)) {
- printf("failed to break apart source image\n");
+
+ if (!ZipModeImage::CheckAndProcessChunks(&tgt_image, &src_image)) {
return 1;
}
- if (!ReadImage(argv[2], &tgt_chunks, &tgt_file)) {
- printf("failed to break apart target image\n");
+ // Compute bsdiff patches for each chunk's data (the uncompressed data, in the case of
+ // deflate chunks).
+ if (!ZipModeImage::GeneratePatches(tgt_image, src_image, argv[optind + 2])) {
return 1;
}
+ } else {
+ ImageModeImage src_image(true);
+ ImageModeImage tgt_image(false);
- // Verify that the source and target images have the same chunk
- // structure (ie, the same sequence of deflate and normal chunks).
-
- // Merge the gzip header and footer in with any adjacent normal chunks.
- MergeAdjacentNormalChunks(&tgt_chunks);
- MergeAdjacentNormalChunks(&src_chunks);
-
- if (src_chunks.size() != tgt_chunks.size()) {
- printf("source and target don't have same number of chunks!\n");
- printf("source chunks:\n");
- DumpChunks(src_chunks);
- printf("target chunks:\n");
- DumpChunks(tgt_chunks);
+ if (!src_image.Initialize(argv[optind])) {
return 1;
}
- for (size_t i = 0; i < src_chunks.size(); ++i) {
- if (src_chunks[i].GetType() != tgt_chunks[i].GetType()) {
- printf("source and target don't have same chunk structure! (chunk %zu)\n", i);
- printf("source chunks:\n");
- DumpChunks(src_chunks);
- printf("target chunks:\n");
- DumpChunks(tgt_chunks);
- return 1;
- }
- }
- }
-
- for (size_t i = 0; i < tgt_chunks.size(); ++i) {
- if (tgt_chunks[i].GetType() == CHUNK_DEFLATE) {
- // Confirm that given the uncompressed chunk data in the target, we
- // can recompress it and get exactly the same bits as are in the
- // input target image. If this fails, treat the chunk as a normal
- // non-deflated chunk.
- if (!tgt_chunks[i].ReconstructDeflateChunk()) {
- printf("failed to reconstruct target deflate chunk %zu [%s]; treating as normal\n", i,
- tgt_chunks[i].GetEntryName().c_str());
- tgt_chunks[i].ChangeDeflateChunkToNormal();
- if (zip_mode) {
- ImageChunk* src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks);
- if (src != nullptr) {
- src->ChangeDeflateChunkToNormal();
- }
- } else {
- src_chunks[i].ChangeDeflateChunkToNormal();
- }
- continue;
- }
-
- // If two deflate chunks are identical (eg, the kernel has not
- // changed between two builds), treat them as normal chunks.
- // This makes applypatch much faster -- it can apply a trivial
- // patch to the compressed data, rather than uncompressing and
- // recompressing to apply the trivial patch to the uncompressed
- // data.
- ImageChunk* src;
- if (zip_mode) {
- src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks);
- } else {
- src = &src_chunks[i];
- }
-
- if (src == nullptr) {
- tgt_chunks[i].ChangeDeflateChunkToNormal();
- } else if (tgt_chunks[i] == *src) {
- tgt_chunks[i].ChangeDeflateChunkToNormal();
- src->ChangeDeflateChunkToNormal();
- }
+ if (!tgt_image.Initialize(argv[optind + 1])) {
+ return 1;
}
- }
- // Merging neighboring normal chunks.
- if (zip_mode) {
- // For zips, we only need to do this to the target: deflated
- // chunks are matched via filename, and normal chunks are patched
- // using the entire source file as the source.
- MergeAdjacentNormalChunks(&tgt_chunks);
-
- } else {
- // For images, we need to maintain the parallel structure of the
- // chunk lists, so do the merging in both the source and target
- // lists.
- MergeAdjacentNormalChunks(&tgt_chunks);
- MergeAdjacentNormalChunks(&src_chunks);
- if (src_chunks.size() != tgt_chunks.size()) {
- // This shouldn't happen.
- printf("merging normal chunks went awry\n");
+ if (!ImageModeImage::CheckAndProcessChunks(&tgt_image, &src_image)) {
return 1;
}
- }
- // Compute bsdiff patches for each chunk's data (the uncompressed
- // data, in the case of deflate chunks).
-
- DumpChunks(src_chunks);
-
- printf("Construct patches for %zu chunks...\n", tgt_chunks.size());
- std::vector<std::vector<uint8_t>> patch_data(tgt_chunks.size());
- saidx_t* bsdiff_cache = nullptr;
- for (size_t i = 0; i < tgt_chunks.size(); ++i) {
- if (zip_mode) {
- ImageChunk* src;
- if (tgt_chunks[i].GetType() == CHUNK_DEFLATE &&
- (src = FindChunkByName(tgt_chunks[i].GetEntryName(), src_chunks))) {
- if (!MakePatch(src, &tgt_chunks[i], &patch_data[i], nullptr)) {
- printf("Failed to generate patch for target chunk %zu: ", i);
- return 1;
- }
- } else {
- if (!MakePatch(&src_chunks[0], &tgt_chunks[i], &patch_data[i], &bsdiff_cache)) {
- printf("Failed to generate patch for target chunk %zu: ", i);
- return 1;
- }
- }
- } else {
- if (i == 1 && !bonus_data.empty()) {
- printf(" using %zu bytes of bonus data for chunk %zu\n", bonus_data.size(), i);
- src_chunks[i].SetBonusData(bonus_data);
- }
-
- if (!MakePatch(&src_chunks[i], &tgt_chunks[i], &patch_data[i], nullptr)) {
- printf("Failed to generate patch for target chunk %zu: ", i);
- return 1;
- }
+ if (!bonus_data.empty() && !src_image.SetBonusData(bonus_data)) {
+ return 1;
}
- printf("patch %3zu is %zu bytes (of %zu)\n", i, patch_data[i].size(),
- src_chunks[i].GetRawDataLength());
- }
- if (bsdiff_cache != nullptr) {
- free(bsdiff_cache);
- }
-
- // Figure out how big the imgdiff file header is going to be, so
- // that we can correctly compute the offset of each bsdiff patch
- // within the file.
-
- size_t total_header_size = 12;
- for (size_t i = 0; i < tgt_chunks.size(); ++i) {
- total_header_size += tgt_chunks[i].GetHeaderSize(patch_data[i].size());
- }
-
- size_t offset = total_header_size;
-
- android::base::unique_fd patch_fd(open(argv[3], O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR));
- if (patch_fd == -1) {
- printf("failed to open \"%s\": %s\n", argv[3], strerror(errno));
- return 1;
- }
-
- // Write out the headers.
- if (!android::base::WriteStringToFd("IMGDIFF2", patch_fd)) {
- printf("failed to write \"IMGDIFF2\" to \"%s\": %s\n", argv[3], strerror(errno));
- return 1;
- }
- Write4(patch_fd, static_cast<int32_t>(tgt_chunks.size()));
- for (size_t i = 0; i < tgt_chunks.size(); ++i) {
- printf("chunk %zu: ", i);
- offset = tgt_chunks[i].WriteHeaderToFd(patch_fd, patch_data[i], offset);
- }
-
- // Append each chunk's bsdiff patch, in order.
- for (size_t i = 0; i < tgt_chunks.size(); ++i) {
- if (tgt_chunks[i].GetType() != CHUNK_RAW) {
- if (!android::base::WriteFully(patch_fd, patch_data[i].data(), patch_data[i].size())) {
- CHECK(false) << "failed to write " << patch_data[i].size() << " bytes patch for chunk "
- << i;
- }
+ if (!ImageModeImage::GeneratePatches(tgt_image, src_image, argv[optind + 2])) {
+ return 1;
}
}
diff --git a/applypatch/include/applypatch/imgdiff_image.h b/applypatch/include/applypatch/imgdiff_image.h
new file mode 100644
index 000000000..221dd5ab5
--- /dev/null
+++ b/applypatch/include/applypatch/imgdiff_image.h
@@ -0,0 +1,247 @@
+/*
+ * 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.
+ */
+
+#ifndef _APPLYPATCH_IMGDIFF_IMAGE_H
+#define _APPLYPATCH_IMGDIFF_IMAGE_H
+
+#include <stddef.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <bsdiff.h>
+#include <ziparchive/zip_archive.h>
+#include <zlib.h>
+
+#include "imgdiff.h"
+#include "rangeset.h"
+
+class ImageChunk {
+ public:
+ static constexpr auto WINDOWBITS = -15; // 32kb window; negative to indicate a raw stream.
+ static constexpr auto MEMLEVEL = 8; // the default value.
+ static constexpr auto METHOD = Z_DEFLATED;
+ static constexpr auto STRATEGY = Z_DEFAULT_STRATEGY;
+
+ ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content, size_t raw_data_len,
+ std::string entry_name = {});
+
+ int GetType() const {
+ return type_;
+ }
+ size_t GetRawDataLength() const {
+ return raw_data_len_;
+ }
+ const std::string& GetEntryName() const {
+ return entry_name_;
+ }
+ size_t GetStartOffset() const {
+ return start_;
+ }
+ int GetCompressLevel() const {
+ return compress_level_;
+ }
+
+ // CHUNK_DEFLATE will return the uncompressed data for diff, while other types will simply return
+ // the raw data.
+ const uint8_t* DataForPatch() const;
+ size_t DataLengthForPatch() const;
+
+ void Dump() const {
+ printf("type: %d, start: %zu, len: %zu, name: %s\n", type_, start_, DataLengthForPatch(),
+ entry_name_.c_str());
+ }
+
+ void SetUncompressedData(std::vector<uint8_t> data);
+ bool SetBonusData(const std::vector<uint8_t>& bonus_data);
+
+ bool operator==(const ImageChunk& other) const;
+ bool operator!=(const ImageChunk& other) const {
+ return !(*this == other);
+ }
+
+ /*
+ * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob of uninterpreted data).
+ * The resulting patch will likely be about as big as the target file, but it lets us handle
+ * the case of images where some gzip chunks are reconstructible but others aren't (by treating
+ * the ones that aren't as normal chunks).
+ */
+ void ChangeDeflateChunkToNormal();
+
+ /*
+ * Verify that we can reproduce exactly the same compressed data that we started with. Sets the
+ * level, method, windowBits, memLevel, and strategy fields in the chunk to the encoding
+ * parameters needed to produce the right output.
+ */
+ bool ReconstructDeflateChunk();
+ bool IsAdjacentNormal(const ImageChunk& other) const;
+ void MergeAdjacentNormal(const ImageChunk& other);
+
+ /*
+ * Compute a bsdiff patch between |src| and |tgt|; Store the result in the patch_data.
+ * |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk is used
+ * repeatedly, pass nullptr if not needed.
+ */
+ static bool MakePatch(const ImageChunk& tgt, const ImageChunk& src,
+ std::vector<uint8_t>* patch_data, saidx_t** bsdiff_cache);
+
+ private:
+ const uint8_t* GetRawData() const;
+ bool TryReconstruction(int level);
+
+ int type_; // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
+ size_t start_; // offset of chunk in the original input file
+ const std::vector<uint8_t>* input_file_ptr_; // ptr to the full content of original input file
+ size_t raw_data_len_;
+
+ // deflate encoder parameters
+ int compress_level_;
+
+ // --- for CHUNK_DEFLATE chunks only: ---
+ std::vector<uint8_t> uncompressed_data_;
+ std::string entry_name_; // used for zip entries
+};
+
+// PatchChunk stores the patch data between a source chunk and a target chunk. It also keeps track
+// of the metadata of src&tgt chunks (e.g. offset, raw data length, uncompressed data length).
+class PatchChunk {
+ public:
+ PatchChunk(const ImageChunk& tgt, const ImageChunk& src, std::vector<uint8_t> data);
+
+ // Construct a CHUNK_RAW patch from the target data directly.
+ explicit PatchChunk(const ImageChunk& tgt);
+
+ // Return true if raw data size is smaller than the patch size.
+ static bool RawDataIsSmaller(const ImageChunk& tgt, size_t patch_size);
+
+ static bool WritePatchDataToFd(const std::vector<PatchChunk>& patch_chunks, int patch_fd);
+
+ private:
+ size_t GetHeaderSize() const;
+ size_t WriteHeaderToFd(int fd, size_t offset) const;
+
+ // The patch chunk type is the same as the target chunk type. The only exception is we change
+ // the |type_| to CHUNK_RAW if target length is smaller than the patch size.
+ int type_;
+
+ size_t source_start_;
+ size_t source_len_;
+ size_t source_uncompressed_len_;
+
+ size_t target_start_; // offset of the target chunk within the target file
+ size_t target_len_;
+ size_t target_uncompressed_len_;
+ size_t target_compress_level_; // the deflate compression level of the target chunk.
+
+ std::vector<uint8_t> data_; // storage for the patch data
+};
+
+// Interface for zip_mode and image_mode images. We initialize the image from an input file and
+// split the file content into a list of image chunks.
+class Image {
+ public:
+ explicit Image(bool is_source) : is_source_(is_source) {}
+
+ virtual ~Image() {}
+
+ // Create a list of image chunks from input file.
+ virtual bool Initialize(const std::string& filename) = 0;
+
+ // Look for runs of adjacent normal chunks and compress them down into a single chunk. (Such
+ // runs can be produced when deflate chunks are changed to normal chunks.)
+ void MergeAdjacentNormalChunks();
+
+ void DumpChunks() const;
+
+ // Non const iterators to access the stored ImageChunks.
+ std::vector<ImageChunk>::iterator begin() {
+ return chunks_.begin();
+ }
+
+ std::vector<ImageChunk>::iterator end() {
+ return chunks_.end();
+ }
+
+ ImageChunk& operator[](size_t i);
+ const ImageChunk& operator[](size_t i) const;
+
+ size_t NumOfChunks() const {
+ return chunks_.size();
+ }
+
+ protected:
+ bool ReadFile(const std::string& filename, std::vector<uint8_t>* file_content);
+
+ bool is_source_; // True if it's for source chunks.
+ std::vector<ImageChunk> chunks_; // Internal storage of ImageChunk.
+ std::vector<uint8_t> file_content_; // Store the whole input file in memory.
+};
+
+class ZipModeImage : public Image {
+ public:
+ explicit ZipModeImage(bool is_source) : Image(is_source) {}
+
+ bool Initialize(const std::string& filename) override;
+
+ // The pesudo source chunk for bsdiff if there's no match for the given target chunk. It's in
+ // fact the whole source file.
+ ImageChunk PseudoSource() const;
+
+ // Find the matching deflate source chunk by entry name. Search for normal chunks also if
+ // |find_normal| is true.
+ ImageChunk* FindChunkByName(const std::string& name, bool find_normal = false);
+
+ const ImageChunk* FindChunkByName(const std::string& name, bool find_normal = false) const;
+
+ // Verify that we can reconstruct the deflate chunks; also change the type to CHUNK_NORMAL if
+ // src and tgt are identical.
+ static bool CheckAndProcessChunks(ZipModeImage* tgt_image, ZipModeImage* src_image);
+
+ // Compute the patch between tgt & src images, and write the data into |patch_name|.
+ static bool GeneratePatches(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
+ const std::string& patch_name);
+
+ private:
+ // Initialize image chunks based on the zip entries.
+ bool InitializeChunks(const std::string& filename, ZipArchiveHandle handle);
+ // Add the a zip entry to the list.
+ bool AddZipEntryToChunks(ZipArchiveHandle handle, const std::string& entry_name, ZipEntry* entry);
+ // Return the real size of the zip file. (omit the trailing zeros that used for alignment)
+ bool GetZipFileSize(size_t* input_file_size);
+};
+
+class ImageModeImage : public Image {
+ public:
+ explicit ImageModeImage(bool is_source) : Image(is_source) {}
+
+ // Initialize the image chunks list by searching the magic numbers in an image file.
+ bool Initialize(const std::string& filename) override;
+
+ bool SetBonusData(const std::vector<uint8_t>& bonus_data);
+
+ // In Image Mode, verify that the source and target images have the same chunk structure (ie, the
+ // same sequence of deflate and normal chunks).
+ static bool CheckAndProcessChunks(ImageModeImage* tgt_image, ImageModeImage* src_image);
+
+ // In image mode, generate patches against the given source chunks and bonus_data; write the
+ // result to |patch_name|.
+ static bool GeneratePatches(const ImageModeImage& tgt_image, const ImageModeImage& src_image,
+ const std::string& patch_name);
+};
+
+#endif // _APPLYPATCH_IMGDIFF_IMAGE_H
diff --git a/edify/Android.mk b/edify/Android.mk
index d8058c16f..ffd54c208 100644
--- a/edify/Android.mk
+++ b/edify/Android.mk
@@ -34,7 +34,6 @@ LOCAL_MODULE := edify_parser
LOCAL_YACCFLAGS := -v
LOCAL_CPPFLAGS += -Wno-unused-parameter
LOCAL_CPPFLAGS += -Wno-deprecated-register
-LOCAL_CLANG := true
LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
LOCAL_STATIC_LIBRARIES += libbase
@@ -51,7 +50,6 @@ LOCAL_CFLAGS := -Werror
LOCAL_CPPFLAGS := -Wno-unused-parameter
LOCAL_CPPFLAGS += -Wno-deprecated-register
LOCAL_MODULE := libedify
-LOCAL_CLANG := true
LOCAL_C_INCLUDES += $(LOCAL_PATH)/..
LOCAL_STATIC_LIBRARIES += libbase
diff --git a/error_code.h b/error_code.h
index 9fe047c91..4e3032bc9 100644
--- a/error_code.h
+++ b/error_code.h
@@ -25,6 +25,9 @@ enum ErrorCode {
kBootreasonInBlacklist,
kPackageCompatibilityFailure,
kScriptExecutionFailure,
+ kMapFileFailure,
+ kForkUpdateBinaryFailure,
+ kUpdateBinaryCommandFailure,
};
enum CauseCode {
@@ -68,6 +71,8 @@ enum UncryptErrorCode {
kUncryptFileCloseError,
kUncryptFileRenameError,
kUncryptPackageMissingError,
+ kUncryptRealpathFindError,
+ kUncryptBlockDeviceFindError,
};
#endif // _ERROR_CODE_H_
diff --git a/etc/init.rc b/etc/init.rc
index 2e3c7a739..d8121cc4e 100644
--- a/etc/init.rc
+++ b/etc/init.rc
@@ -79,9 +79,9 @@ service ueventd /sbin/ueventd
critical
seclabel u:r:ueventd:s0
-service healthd /sbin/healthd -r
+service charger /charger -r
critical
- seclabel u:r:healthd:s0
+ seclabel u:r:charger:s0
service recovery /sbin/recovery
seclabel u:r:recovery:s0
diff --git a/install.cpp b/install.cpp
index 7ba8f0139..586dbbe2c 100644
--- a/install.cpp
+++ b/install.cpp
@@ -148,13 +148,23 @@ static int check_newer_ab_build(ZipArchiveHandle zip) {
return INSTALL_ERROR;
}
- // We allow the package to not have any serialno, but if it has a non-empty
- // value it should match.
+ // We allow the package to not have any serialno; and we also allow it to carry multiple serial
+ // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the
+ // verification if the device's serialno doesn't match any of these carried numbers.
value = android::base::GetProperty("ro.serialno", "");
const std::string& pkg_serial_no = metadata["serialno"];
- if (!pkg_serial_no.empty() && pkg_serial_no != value) {
- LOG(ERROR) << "Package is for serial " << pkg_serial_no;
- return INSTALL_ERROR;
+ if (!pkg_serial_no.empty()) {
+ bool match = false;
+ for (const std::string& number : android::base::Split(pkg_serial_no, "|")) {
+ if (value == android::base::Trim(number)) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) {
+ LOG(ERROR) << "Package is for serial " << pkg_serial_no;
+ return INSTALL_ERROR;
+ }
}
if (metadata["ota-type"] != "AB") {
@@ -265,7 +275,7 @@ int update_binary_command(const std::string& package, ZipArchiveHandle zip,
}
unlink(binary_path.c_str());
- int fd = creat(binary_path.c_str(), 0755);
+ int fd = open(binary_path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0755);
if (fd == -1) {
PLOG(ERROR) << "Failed to create " << binary_path;
return INSTALL_ERROR;
@@ -321,6 +331,7 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b
if (ret) {
close(pipefd[0]);
close(pipefd[1]);
+ log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
return ret;
}
@@ -385,6 +396,7 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b
close(pipefd[0]);
close(pipefd[1]);
PLOG(ERROR) << "Failed to fork update binary";
+ log_buffer->push_back(android::base::StringPrintf("error: %d", kForkUpdateBinaryFailure));
return INSTALL_ERROR;
}
@@ -573,6 +585,7 @@ static int really_install_package(const std::string& path, bool* wipe_cache, boo
MemMapping map;
if (!map.MapFile(path)) {
LOG(ERROR) << "failed to map file";
+ log_buffer->push_back(android::base::StringPrintf("error: %d", kMapFileFailure));
return INSTALL_CORRUPT;
}
diff --git a/minadbd/Android.mk b/minadbd/Android.mk
index de0b0c890..8d86fd653 100644
--- a/minadbd/Android.mk
+++ b/minadbd/Android.mk
@@ -15,7 +15,6 @@ LOCAL_SRC_FILES := \
minadbd.cpp \
minadbd_services.cpp \
-LOCAL_CLANG := true
LOCAL_MODULE := libminadbd
LOCAL_CFLAGS := $(minadbd_cflags)
LOCAL_CONLY_FLAGS := -Wimplicit-function-declaration
@@ -27,7 +26,6 @@ include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
-LOCAL_CLANG := true
LOCAL_MODULE := minadbd_test
LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_SRC_FILES := fuse_adb_provider_test.cpp
diff --git a/minui/events.cpp b/minui/events.cpp
index 0e1fd44a0..24c2a8277 100644
--- a/minui/events.cpp
+++ b/minui/events.cpp
@@ -53,36 +53,37 @@ static bool test_bit(size_t bit, unsigned long* array) { // NOLINT
return (array[bit/BITS_PER_LONG] & (1UL << (bit % BITS_PER_LONG))) != 0;
}
-int ev_init(ev_callback input_cb) {
- bool epollctlfail = false;
-
+int ev_init(ev_callback input_cb, bool allow_touch_inputs) {
g_epoll_fd = epoll_create(MAX_DEVICES + MAX_MISC_FDS);
if (g_epoll_fd == -1) {
return -1;
}
+ bool epollctlfail = false;
DIR* dir = opendir("/dev/input");
- if (dir != NULL) {
+ if (dir != nullptr) {
dirent* de;
while ((de = readdir(dir))) {
- // Use unsigned long to match ioctl's parameter type.
- unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
-
- // fprintf(stderr,"/dev/input/%s\n", de->d_name);
if (strncmp(de->d_name, "event", 5)) continue;
int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
if (fd == -1) continue;
+ // Use unsigned long to match ioctl's parameter type.
+ unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)]; // NOLINT
+
// Read the evbits of the input device.
if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
close(fd);
continue;
}
- // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed.
+ // We assume that only EV_KEY, EV_REL, and EV_SW event types are ever needed. EV_ABS is also
+ // allowed if allow_touch_inputs is set.
if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) && !test_bit(EV_SW, ev_bits)) {
- close(fd);
- continue;
+ if (!allow_touch_inputs || !test_bit(EV_ABS, ev_bits)) {
+ close(fd);
+ continue;
+ }
}
epoll_event ev;
@@ -231,3 +232,27 @@ void ev_iterate_available_keys(const std::function<void(int)>& f) {
}
}
}
+
+void ev_iterate_touch_inputs(const std::function<void(int)>& action) {
+ for (size_t i = 0; i < ev_dev_count; ++i) {
+ // Use unsigned long to match ioctl's parameter type.
+ unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)] = {}; // NOLINT
+ if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) == -1) {
+ continue;
+ }
+ if (!test_bit(EV_ABS, ev_bits)) {
+ continue;
+ }
+
+ unsigned long key_bits[BITS_TO_LONGS(KEY_MAX)] = {}; // NOLINT
+ if (ioctl(ev_fdinfo[i].fd, EVIOCGBIT(EV_ABS, KEY_MAX), key_bits) == -1) {
+ continue;
+ }
+
+ for (int key_code = 0; key_code <= KEY_MAX; ++key_code) {
+ if (test_bit(key_code, key_bits)) {
+ action(key_code);
+ }
+ }
+ }
+}
diff --git a/minui/graphics.cpp b/minui/graphics.cpp
index 3bdc33fd1..3bfce11d8 100644
--- a/minui/graphics.cpp
+++ b/minui/graphics.cpp
@@ -258,7 +258,7 @@ unsigned int gr_get_height(GRSurface* surface) {
}
int gr_init_font(const char* name, GRFont** dest) {
- GRFont* font = reinterpret_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
+ GRFont* font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
if (font == nullptr) {
return -1;
}
@@ -291,7 +291,7 @@ static void gr_init_font(void)
// fall back to the compiled-in font.
- gr_font = static_cast<GRFont*>(calloc(sizeof(*gr_font), 1));
+ gr_font = static_cast<GRFont*>(calloc(1, sizeof(*gr_font)));
gr_font->texture = static_cast<GRSurface*>(malloc(sizeof(*gr_font->texture)));
gr_font->texture->width = font.width;
gr_font->texture->height = font.height;
diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h
index 78dd4cb98..017ddde75 100644
--- a/minui/include/minui/minui.h
+++ b/minui/include/minui/minui.h
@@ -74,10 +74,11 @@ struct input_event;
using ev_callback = std::function<int(int fd, uint32_t epevents)>;
using ev_set_key_callback = std::function<int(int code, int value)>;
-int ev_init(ev_callback input_cb);
+int ev_init(ev_callback input_cb, bool allow_touch_inputs = false);
void ev_exit();
int ev_add_fd(int fd, ev_callback cb);
void ev_iterate_available_keys(const std::function<void(int)>& f);
+void ev_iterate_touch_inputs(const std::function<void(int)>& action);
int ev_sync_key_state(const ev_set_key_callback& set_key_cb);
// 'timeout' has the same semantics as poll(2).
diff --git a/minui/resources.cpp b/minui/resources.cpp
index 86c731b02..8f8d36d27 100644
--- a/minui/resources.cpp
+++ b/minui/resources.cpp
@@ -56,7 +56,7 @@ static int open_png(const char* name, png_structp* png_ptr, png_infop* info_ptr,
snprintf(resPath, sizeof(resPath)-1, "/res/images/%s.png", name);
resPath[sizeof(resPath)-1] = '\0';
- FILE* fp = fopen(resPath, "rb");
+ FILE* fp = fopen(resPath, "rbe");
if (fp == NULL) {
result = -1;
goto exit;
diff --git a/otafault/Android.mk b/otafault/Android.mk
index ec4cdb365..7b5aab0b8 100644
--- a/otafault/Android.mk
+++ b/otafault/Android.mk
@@ -32,7 +32,6 @@ LOCAL_CFLAGS := \
LOCAL_SRC_FILES := config.cpp ota_io.cpp
LOCAL_MODULE_TAGS := eng
LOCAL_MODULE := libotafault
-LOCAL_CLANG := true
LOCAL_C_INCLUDES := bootable/recovery
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
LOCAL_WHOLE_STATIC_LIBRARIES := $(otafault_static_libs)
diff --git a/otautil/DirUtil.cpp b/otautil/DirUtil.cpp
index e08e360c0..fffc82219 100644
--- a/otautil/DirUtil.cpp
+++ b/otautil/DirUtil.cpp
@@ -16,203 +16,101 @@
#include "DirUtil.h"
+#include <dirent.h>
+#include <errno.h>
#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <sys/types.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
-#include <errno.h>
-#include <dirent.h>
-#include <limits.h>
#include <string>
#include <selinux/label.h>
#include <selinux/selinux.h>
-typedef enum { DMISSING, DDIR, DILLEGAL } DirStatus;
+enum class DirStatus { DMISSING, DDIR, DILLEGAL };
-static DirStatus
-getPathDirStatus(const char *path)
-{
- struct stat st;
- int err;
-
- err = stat(path, &st);
- if (err == 0) {
- /* Something's there; make sure it's a directory.
- */
- if (S_ISDIR(st.st_mode)) {
- return DDIR;
- }
- errno = ENOTDIR;
- return DILLEGAL;
- } else if (errno != ENOENT) {
- /* Something went wrong, or something in the path
- * is bad. Can't do anything in this situation.
- */
- return DILLEGAL;
+static DirStatus dir_status(const std::string& path) {
+ struct stat sb;
+ if (stat(path.c_str(), &sb) == 0) {
+ // Something's there; make sure it's a directory.
+ if (S_ISDIR(sb.st_mode)) {
+ return DirStatus::DDIR;
}
- return DMISSING;
+ errno = ENOTDIR;
+ return DirStatus::DILLEGAL;
+ } else if (errno != ENOENT) {
+ // Something went wrong, or something in the path is bad. Can't do anything in this situation.
+ return DirStatus::DILLEGAL;
+ }
+ return DirStatus::DMISSING;
}
-int
-dirCreateHierarchy(const char *path, int mode,
- const struct utimbuf *timestamp, bool stripFileName,
- struct selabel_handle *sehnd)
-{
- DirStatus ds;
-
- /* Check for an empty string before we bother
- * making any syscalls.
- */
- if (path[0] == '\0') {
- errno = ENOENT;
- return -1;
- }
- // Allocate a path that we can modify; stick a slash on
- // the end to make things easier.
- std::string cpath = path;
- if (stripFileName) {
- // Strip everything after the last slash.
- size_t pos = cpath.rfind('/');
- if (pos == std::string::npos) {
- errno = ENOENT;
- return -1;
- }
- cpath.resize(pos + 1);
- } else {
- // Make sure that the path ends in a slash.
- cpath.push_back('/');
- }
-
- /* See if it already exists.
- */
- ds = getPathDirStatus(cpath.c_str());
- if (ds == DDIR) {
- return 0;
- } else if (ds == DILLEGAL) {
- return -1;
- }
-
- /* Walk up the path from the root and make each level.
- * If a directory already exists, no big deal.
- */
- const char *path_start = &cpath[0];
- char *p = &cpath[0];
- while (*p != '\0') {
- /* Skip any slashes, watching out for the end of the string.
- */
- while (*p != '\0' && *p == '/') {
- p++;
- }
- if (*p == '\0') {
- break;
- }
-
- /* Find the end of the next path component.
- * We know that we'll see a slash before the NUL,
- * because we added it, above.
- */
- while (*p != '/') {
- p++;
- }
- *p = '\0';
-
- /* Check this part of the path and make a new directory
- * if necessary.
- */
- ds = getPathDirStatus(path_start);
- if (ds == DILLEGAL) {
- /* Could happen if some other process/thread is
- * messing with the filesystem.
- */
- return -1;
- } else if (ds == DMISSING) {
- int err;
-
- char *secontext = NULL;
-
- if (sehnd) {
- selabel_lookup(sehnd, &secontext, path_start, mode);
- setfscreatecon(secontext);
- }
-
- err = mkdir(path_start, mode);
-
- if (secontext) {
- freecon(secontext);
- setfscreatecon(NULL);
- }
-
- if (err != 0) {
- return -1;
- }
- if (timestamp != NULL && utime(path_start, timestamp)) {
- return -1;
- }
- }
- // else, this directory already exists.
-
- // Repair the path and continue.
- *p = '/';
+int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename,
+ const selabel_handle* sehnd) {
+ // Check for an empty string before we bother making any syscalls.
+ if (input_path.empty()) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ // Allocate a path that we can modify; stick a slash on the end to make things easier.
+ std::string path = input_path;
+ if (strip_filename) {
+ // Strip everything after the last slash.
+ size_t pos = path.rfind('/');
+ if (pos == std::string::npos) {
+ errno = ENOENT;
+ return -1;
}
+ path.resize(pos + 1);
+ } else {
+ // Make sure that the path ends in a slash.
+ path.push_back('/');
+ }
+
+ // See if it already exists.
+ DirStatus ds = dir_status(path);
+ if (ds == DirStatus::DDIR) {
return 0;
-}
-
-int
-dirUnlinkHierarchy(const char *path)
-{
- struct stat st;
- DIR *dir;
- struct dirent *de;
- int fail = 0;
-
- /* is it a file or directory? */
- if (lstat(path, &st) < 0) {
- return -1;
- }
-
- /* a file, so unlink it */
- if (!S_ISDIR(st.st_mode)) {
- return unlink(path);
+ } else if (ds == DirStatus::DILLEGAL) {
+ return -1;
+ }
+
+ // Walk up the path from the root and make each level.
+ size_t prev_end = 0;
+ while (prev_end < path.size()) {
+ size_t next_end = path.find('/', prev_end + 1);
+ if (next_end == std::string::npos) {
+ break;
}
-
- /* a directory, so open handle */
- dir = opendir(path);
- if (dir == NULL) {
+ std::string dir_path = path.substr(0, next_end);
+ // Check this part of the path and make a new directory if necessary.
+ switch (dir_status(dir_path)) {
+ case DirStatus::DILLEGAL:
+ // Could happen if some other process/thread is messing with the filesystem.
return -1;
- }
-
- /* recurse over components */
- errno = 0;
- while ((de = readdir(dir)) != NULL) {
- //TODO: don't blow the stack
- char dn[PATH_MAX];
- if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
- continue;
+ case DirStatus::DMISSING: {
+ char* secontext = nullptr;
+ if (sehnd) {
+ selabel_lookup(const_cast<selabel_handle*>(sehnd), &secontext, dir_path.c_str(), mode);
+ setfscreatecon(secontext);
}
- snprintf(dn, sizeof(dn), "%s/%s", path, de->d_name);
- if (dirUnlinkHierarchy(dn) < 0) {
- fail = 1;
- break;
+ int err = mkdir(dir_path.c_str(), mode);
+ if (secontext) {
+ freecon(secontext);
+ setfscreatecon(nullptr);
}
- errno = 0;
- }
- /* in case readdir or unlink_recursive failed */
- if (fail || errno < 0) {
- int save = errno;
- closedir(dir);
- errno = save;
- return -1;
- }
-
- /* close directory handle */
- if (closedir(dir) < 0) {
- return -1;
+ if (err != 0) {
+ return -1;
+ }
+ break;
+ }
+ default:
+ // Already exists.
+ break;
}
-
- /* delete target directory */
- return rmdir(path);
+ prev_end = next_end;
+ }
+ return 0;
}
diff --git a/otautil/DirUtil.h b/otautil/DirUtil.h
index 85b83c387..85d6c16d1 100644
--- a/otautil/DirUtil.h
+++ b/otautil/DirUtil.h
@@ -14,41 +14,26 @@
* limitations under the License.
*/
-#ifndef MINZIP_DIRUTIL_H_
-#define MINZIP_DIRUTIL_H_
+#ifndef OTAUTIL_DIRUTIL_H_
+#define OTAUTIL_DIRUTIL_H_
-#include <stdbool.h>
-#include <utime.h>
+#include <sys/stat.h> // mode_t
-#ifdef __cplusplus
-extern "C" {
-#endif
+#include <string>
struct selabel_handle;
-/* Like "mkdir -p", try to guarantee that all directories
- * specified in path are present, creating as many directories
- * as necessary. The specified mode is passed to all mkdir
- * calls; no modifications are made to umask.
- *
- * If stripFileName is set, everything after the final '/'
- * is stripped before creating the directory hierarchy.
- *
- * If timestamp is non-NULL, new directories will be timestamped accordingly.
- *
- * Returns 0 on success; returns -1 (and sets errno) on failure
- * (usually if some element of path is not a directory).
- */
-int dirCreateHierarchy(const char *path, int mode,
- const struct utimbuf *timestamp, bool stripFileName,
- struct selabel_handle* sehnd);
-
-/* rm -rf <path>
- */
-int dirUnlinkHierarchy(const char *path);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // MINZIP_DIRUTIL_H_
+// Like "mkdir -p", try to guarantee that all directories specified in path are present, creating as
+// many directories as necessary. The specified mode is passed to all mkdir calls; no modifications
+// are made to umask.
+//
+// If strip_filename is set, everything after the final '/' is stripped before creating the
+// directory
+// hierarchy.
+//
+// Returns 0 on success; returns -1 (and sets errno) on failure (usually if some element of path is
+// not a directory).
+int mkdir_recursively(const std::string& path, mode_t mode, bool strip_filename,
+ const struct selabel_handle* sehnd);
+
+#endif // OTAUTIL_DIRUTIL_H_
diff --git a/updater/include/updater/rangeset.h b/rangeset.h
index fad038043..f224a08be 100644
--- a/updater/include/updater/rangeset.h
+++ b/rangeset.h
@@ -24,6 +24,7 @@
#include <android-base/logging.h>
#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
#include <android-base/strings.h>
using Range = std::pair<size_t, size_t>;
@@ -74,6 +75,18 @@ class RangeSet {
return RangeSet(std::move(pairs));
}
+ std::string ToString() const {
+ if (ranges_.empty()) {
+ return "";
+ }
+ std::string result = std::to_string(ranges_.size() * 2);
+ for (const auto& r : ranges_) {
+ result += android::base::StringPrintf(",%zu,%zu", r.first, r.second);
+ }
+
+ return result;
+ }
+
// 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_ << ")";
@@ -157,8 +170,109 @@ class RangeSet {
return ranges_ != other.ranges_;
}
- private:
+ protected:
// Actual limit for each value and the total number are both INT_MAX.
std::vector<Range> ranges_;
size_t blocks_;
};
+
+static constexpr size_t kBlockSize = 4096;
+
+// The class is a sorted version of a RangeSet; and it's useful in imgdiff to split the input
+// files when we're handling large zip files. Specifically, we can treat the input file as a
+// continuous RangeSet (i.e. RangeSet("0-99") for a 100 blocks file); and break it down into
+// several smaller chunks based on the zip entries.
+
+// For example, [source: 0-99] can be split into
+// [split_src1: 10-29]; [split_src2: 40-49, 60-69]; [split_src3: 70-89]
+// Here "10-29" simply means block 10th to block 29th with respect to the original input file.
+// Also, note that the split sources should be mutual exclusive, but they don't need to cover
+// every block in the original source.
+class SortedRangeSet : public RangeSet {
+ public:
+ SortedRangeSet() {}
+
+ // Ranges in the the set should be mutually exclusive; and they're sorted by the start block.
+ explicit SortedRangeSet(std::vector<Range>&& pairs) : RangeSet(std::move(pairs)) {
+ std::sort(ranges_.begin(), ranges_.end());
+ }
+
+ void Insert(const Range& to_insert) {
+ SortedRangeSet rs({ to_insert });
+ Insert(rs);
+ }
+
+ // Insert the input SortedRangeSet; keep the ranges sorted and merge the overlap ranges.
+ void Insert(const SortedRangeSet& rs) {
+ if (rs.size() == 0) {
+ return;
+ }
+ // Merge and sort the two RangeSets.
+ std::vector<Range> temp = std::move(ranges_);
+ std::copy(rs.begin(), rs.end(), std::back_inserter(temp));
+ std::sort(temp.begin(), temp.end());
+
+ Clear();
+ // Trim overlaps and insert the result back to ranges_.
+ Range to_insert = temp.front();
+ for (auto it = temp.cbegin() + 1; it != temp.cend(); it++) {
+ if (it->first <= to_insert.second) {
+ to_insert.second = std::max(to_insert.second, it->second);
+ } else {
+ ranges_.push_back(to_insert);
+ blocks_ += (to_insert.second - to_insert.first);
+ to_insert = *it;
+ }
+ }
+ ranges_.push_back(to_insert);
+ blocks_ += (to_insert.second - to_insert.first);
+ }
+
+ void Clear() {
+ blocks_ = 0;
+ ranges_.clear();
+ }
+
+ using RangeSet::Overlaps;
+ bool Overlaps(size_t start, size_t len) const {
+ RangeSet rs({ { start / kBlockSize, (start + len - 1) / kBlockSize + 1 } });
+ return Overlaps(rs);
+ }
+
+ // Compute the block range the file occupies, and insert that range.
+ void Insert(size_t start, size_t len) {
+ Range to_insert{ start / kBlockSize, (start + len - 1) / kBlockSize + 1 };
+ Insert(to_insert);
+ }
+
+ // Given an offset of the file, checks if the corresponding block (by considering the file as
+ // 0-based continuous block ranges) is covered by the SortedRangeSet. If so, returns the offset
+ // within this SortedRangeSet.
+ //
+ // For example, the 4106-th byte of a file is from block 1, assuming a block size of 4096-byte.
+ // The mapped offset within a SortedRangeSet("1-9 15-19") is 10.
+ //
+ // An offset of 65546 falls into the 16-th block in a file. Block 16 is contained as the 10-th
+ // item in SortedRangeSet("1-9 15-19"). So its data can be found at offset 40970 (i.e. 4096 * 10
+ // + 10) in a range represented by this SortedRangeSet.
+ size_t GetOffsetInRangeSet(size_t old_offset) const {
+ size_t old_block_start = old_offset / kBlockSize;
+ size_t new_block_start = 0;
+ for (const auto& range : ranges_) {
+ // Find the index of old_block_start.
+ if (old_block_start >= range.second) {
+ new_block_start += (range.second - range.first);
+ } else if (old_block_start >= range.first) {
+ new_block_start += (old_block_start - range.first);
+ return (new_block_start * kBlockSize + old_offset % kBlockSize);
+ } else {
+ CHECK(false) << "block_start " << old_block_start
+ << " is missing between two ranges: " << this->ToString();
+ return 0;
+ }
+ }
+ CHECK(false) << "block_start " << old_block_start
+ << " exceeds the limit of current RangeSet: " << this->ToString();
+ return 0;
+ }
+}; \ No newline at end of file
diff --git a/recovery-persist.cpp b/recovery-persist.cpp
index d706ccac8..dbce7ff74 100644
--- a/recovery-persist.cpp
+++ b/recovery-persist.cpp
@@ -59,21 +59,21 @@ static void check_and_fclose(FILE *fp, const char *name) {
}
static void copy_file(const char* source, const char* destination) {
- FILE* dest_fp = fopen(destination, "w");
- if (dest_fp == nullptr) {
- PLOG(ERROR) << "Can't open " << destination;
- } else {
- FILE* source_fp = fopen(source, "r");
- if (source_fp != nullptr) {
- char buf[4096];
- size_t bytes;
- while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
- fwrite(buf, 1, bytes, dest_fp);
- }
- check_and_fclose(source_fp, source);
- }
- check_and_fclose(dest_fp, destination);
+ FILE* dest_fp = fopen(destination, "we");
+ if (dest_fp == nullptr) {
+ PLOG(ERROR) << "Can't open " << destination;
+ } else {
+ FILE* source_fp = fopen(source, "re");
+ if (source_fp != nullptr) {
+ char buf[4096];
+ size_t bytes;
+ while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
+ fwrite(buf, 1, bytes, dest_fp);
+ }
+ check_and_fclose(source_fp, source);
}
+ check_and_fclose(dest_fp, destination);
+ }
}
static bool rotated = false;
@@ -120,7 +120,7 @@ int main(int argc, char **argv) {
*/
bool has_cache = false;
static const char mounts_file[] = "/proc/mounts";
- FILE *fp = fopen(mounts_file, "r");
+ FILE* fp = fopen(mounts_file, "re");
if (!fp) {
PLOG(ERROR) << "failed to open " << mounts_file;
} else {
diff --git a/recovery.cpp b/recovery.cpp
index 122b89d0b..6f62ff17c 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -53,6 +53,7 @@
#include <healthd/BatteryMonitor.h>
#include <private/android_logger.h> /* private pmsg functions */
#include <private/android_filesystem_config.h> /* for AID_SYSTEM */
+#include <selinux/android.h>
#include <selinux/label.h>
#include <selinux/selinux.h>
#include <ziparchive/zip_archive.h>
@@ -178,19 +179,19 @@ struct selabel_handle* sehandle;
* 7b. the user reboots (pulling the battery, etc) into the main system
*/
-// open a given path, mounting partitions as necessary
-FILE* fopen_path(const char *path, const char *mode) {
- if (ensure_path_mounted(path) != 0) {
- LOG(ERROR) << "Can't mount " << path;
- return NULL;
- }
-
- // When writing, try to create the containing directory, if necessary.
- // Use generous permissions, the system (init.rc) will reset them.
- if (strchr("wa", mode[0])) dirCreateHierarchy(path, 0777, NULL, 1, sehandle);
+// Open a given path, mounting partitions as necessary.
+FILE* fopen_path(const char* path, const char* mode) {
+ if (ensure_path_mounted(path) != 0) {
+ LOG(ERROR) << "Can't mount " << path;
+ return nullptr;
+ }
- FILE *fp = fopen(path, mode);
- return fp;
+ // When writing, try to create the containing directory, if necessary. Use generous permissions,
+ // the system (init.rc) will reset them.
+ if (strchr("wa", mode[0])) {
+ mkdir_recursively(path, 0777, true, sehandle);
+ }
+ return fopen(path, mode);
}
// close a file, log an error if the error indicator is set
@@ -249,7 +250,7 @@ static void redirect_stdio(const char* filename) {
auto start = std::chrono::steady_clock::now();
// Child logger to actually write to the log file.
- FILE* log_fp = fopen(filename, "a");
+ FILE* log_fp = fopen(filename, "ae");
if (log_fp == nullptr) {
PLOG(ERROR) << "fopen \"" << filename << "\" failed";
close(pipefd[0]);
@@ -418,27 +419,27 @@ static void copy_log_file_to_pmsg(const char* source, const char* destination) {
static off_t tmplog_offset = 0;
static void copy_log_file(const char* source, const char* destination, bool append) {
- FILE* dest_fp = fopen_path(destination, append ? "a" : "w");
- if (dest_fp == nullptr) {
- PLOG(ERROR) << "Can't open " << destination;
- } else {
- FILE* source_fp = fopen(source, "r");
- if (source_fp != nullptr) {
- if (append) {
- fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write
- }
- char buf[4096];
- size_t bytes;
- while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
- fwrite(buf, 1, bytes, dest_fp);
- }
- if (append) {
- tmplog_offset = ftello(source_fp);
- }
- check_and_fclose(source_fp, source);
- }
- check_and_fclose(dest_fp, destination);
+ FILE* dest_fp = fopen_path(destination, append ? "ae" : "we");
+ if (dest_fp == nullptr) {
+ PLOG(ERROR) << "Can't open " << destination;
+ } else {
+ FILE* source_fp = fopen(source, "re");
+ if (source_fp != nullptr) {
+ if (append) {
+ fseeko(source_fp, tmplog_offset, SEEK_SET); // Since last write
+ }
+ char buf[4096];
+ size_t bytes;
+ while ((bytes = fread(buf, 1, sizeof(buf), source_fp)) != 0) {
+ fwrite(buf, 1, bytes, dest_fp);
+ }
+ if (append) {
+ tmplog_offset = ftello(source_fp);
+ }
+ check_and_fclose(source_fp, source);
}
+ check_and_fclose(dest_fp, destination);
+ }
}
static void copy_logs() {
@@ -477,40 +478,38 @@ static void copy_logs() {
sync();
}
-// clear the recovery command and prepare to boot a (hopefully working) system,
+// Clear the recovery command and prepare to boot a (hopefully working) system,
// copy our log file to cache as well (for the system to read). This function is
// idempotent: call it as many times as you like.
static void finish_recovery() {
- // Save the locale to cache, so if recovery is next started up
- // without a --locale argument (eg, directly from the bootloader)
- // it will use the last-known locale.
- if (!locale.empty() && has_cache) {
- LOG(INFO) << "Saving locale \"" << locale << "\"";
-
- FILE* fp = fopen_path(LOCALE_FILE, "w");
- if (!android::base::WriteStringToFd(locale, fileno(fp))) {
- PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
- }
- check_and_fclose(fp, LOCALE_FILE);
+ // Save the locale to cache, so if recovery is next started up without a '--locale' argument
+ // (e.g., directly from the bootloader) it will use the last-known locale.
+ if (!locale.empty() && has_cache) {
+ LOG(INFO) << "Saving locale \"" << locale << "\"";
+ if (ensure_path_mounted(LOCALE_FILE) != 0) {
+ LOG(ERROR) << "Failed to mount " << LOCALE_FILE;
+ } else if (!android::base::WriteStringToFile(locale, LOCALE_FILE)) {
+ PLOG(ERROR) << "Failed to save locale to " << LOCALE_FILE;
}
+ }
- copy_logs();
+ copy_logs();
- // Reset to normal system boot so recovery won't cycle indefinitely.
- std::string err;
- if (!clear_bootloader_message(&err)) {
- LOG(ERROR) << "Failed to clear BCB message: " << err;
- }
+ // Reset to normal system boot so recovery won't cycle indefinitely.
+ std::string err;
+ if (!clear_bootloader_message(&err)) {
+ LOG(ERROR) << "Failed to clear BCB message: " << err;
+ }
- // Remove the command file, so recovery won't repeat indefinitely.
- if (has_cache) {
- if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
- LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
- }
- ensure_path_unmounted(CACHE_ROOT);
+ // Remove the command file, so recovery won't repeat indefinitely.
+ if (has_cache) {
+ if (ensure_path_mounted(COMMAND_FILE) != 0 || (unlink(COMMAND_FILE) && errno != ENOENT)) {
+ LOG(WARNING) << "Can't unlink " << COMMAND_FILE;
}
+ ensure_path_unmounted(CACHE_ROOT);
+ }
- sync(); // For good measure.
+ sync(); // For good measure.
}
struct saved_log_file {
@@ -551,7 +550,7 @@ static bool erase_volume(const char* volume) {
}
std::string data(sb.st_size, '\0');
- FILE* f = fopen(path.c_str(), "rb");
+ FILE* f = fopen(path.c_str(), "rbe");
fread(&data[0], 1, data.size(), f);
fclose(f);
@@ -579,7 +578,7 @@ static bool erase_volume(const char* volume) {
ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno));
return true;
}
- FILE* f = fopen(CONVERT_FBE_FILE, "wb");
+ FILE* f = fopen(CONVERT_FBE_FILE, "wbe");
if (!f) {
ui->Print("Failed to convert to file encryption %s\n", strerror(errno));
return true;
@@ -595,7 +594,7 @@ static bool erase_volume(const char* volume) {
if (is_cache) {
// Re-create the log dir and write back the log entries.
if (ensure_path_mounted(CACHE_LOG_DIR) == 0 &&
- dirCreateHierarchy(CACHE_LOG_DIR, 0777, nullptr, false, sehandle) == 0) {
+ mkdir_recursively(CACHE_LOG_DIR, 0777, false, sehandle) == 0) {
for (const auto& log : log_files) {
if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid,
log.sb.st_gid)) {
@@ -759,12 +758,13 @@ static bool wipe_data(Device* device) {
}
static bool prompt_and_wipe_data(Device* device) {
+ // Use a single string and let ScreenRecoveryUI handles the wrapping.
const char* const headers[] = {
- "Can't load Android system. Your data may be corrupt.",
- "If you continue to get this message, you may need to",
- "perform a factory data reset and erase all user data",
+ "Can't load Android system. Your data may be corrupt. "
+ "If you continue to get this message, you may need to "
+ "perform a factory data reset and erase all user data "
"stored on this device.",
- NULL
+ nullptr
};
const char* const items[] = {
"Try again",
@@ -1481,12 +1481,8 @@ int main(int argc, char **argv) {
ui->SetBackground(RecoveryUI::NONE);
if (show_text) ui->ShowText(true);
- struct selinux_opt seopts[] = {
- { SELABEL_OPT_PATH, "/file_contexts" }
- };
-
- sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
-
+ sehandle = selinux_android_file_context_handle();
+ selinux_android_set_sehandle(sehandle);
if (!sehandle) {
ui->Print("Warning: No file_contexts\n");
}
@@ -1596,15 +1592,14 @@ int main(int argc, char **argv) {
ui->Print("Rebooting automatically.\n");
}
} else if (!just_exit) {
- status = INSTALL_NONE; // No command specified
- ui->SetBackground(RecoveryUI::NO_COMMAND);
-
- // http://b/17489952
- // If this is an eng or userdebug build, automatically turn on the
- // text display if no command is specified.
- if (is_ro_debuggable()) {
- ui->ShowText(true);
- }
+ // If this is an eng or userdebug build, automatically turn on the text display if no command
+ // is specified. Note that this should be called before setting the background to avoid
+ // flickering the background image.
+ if (is_ro_debuggable()) {
+ ui->ShowText(true);
+ }
+ status = INSTALL_NONE; // No command specified
+ ui->SetBackground(RecoveryUI::NO_COMMAND);
}
if (status == INSTALL_ERROR || status == INSTALL_CORRUPT) {
diff --git a/roots.cpp b/roots.cpp
index 9b4270256..fdcbfe844 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -16,247 +16,296 @@
#include "roots.h"
-#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
-#include <ctype.h>
-#include <fcntl.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
#include <android-base/logging.h>
-#include <ext4_utils/make_ext4fs.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <cryptfs.h>
#include <ext4_utils/wipe.h>
#include <fs_mgr.h>
#include "common.h"
#include "mounts.h"
-#include "cryptfs.h"
-
-static struct fstab *fstab = NULL;
-extern struct selabel_handle *sehandle;
+static struct fstab* fstab = nullptr;
-void load_volume_table()
-{
- int i;
- int ret;
+extern struct selabel_handle* sehandle;
- fstab = fs_mgr_read_fstab_default();
- if (!fstab) {
- LOG(ERROR) << "failed to read default fstab";
- return;
- }
+void load_volume_table() {
+ fstab = fs_mgr_read_fstab_default();
+ if (!fstab) {
+ LOG(ERROR) << "Failed to read default fstab";
+ return;
+ }
- ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
- if (ret < 0 ) {
- LOG(ERROR) << "failed to add /tmp entry to fstab";
- fs_mgr_free_fstab(fstab);
- fstab = NULL;
- return;
- }
+ int ret = fs_mgr_add_entry(fstab, "/tmp", "ramdisk", "ramdisk");
+ if (ret == -1) {
+ LOG(ERROR) << "Failed to add /tmp entry to fstab";
+ fs_mgr_free_fstab(fstab);
+ fstab = nullptr;
+ return;
+ }
- printf("recovery filesystem table\n");
- printf("=========================\n");
- for (i = 0; i < fstab->num_entries; ++i) {
- Volume* v = &fstab->recs[i];
- printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type,
- v->blk_device, v->length);
- }
- printf("\n");
+ printf("recovery filesystem table\n");
+ printf("=========================\n");
+ for (int i = 0; i < fstab->num_entries; ++i) {
+ const Volume* v = &fstab->recs[i];
+ printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type, v->blk_device, v->length);
+ }
+ printf("\n");
}
Volume* volume_for_path(const char* path) {
- return fs_mgr_get_entry_for_mount_point(fstab, path);
+ return fs_mgr_get_entry_for_mount_point(fstab, path);
}
// Mount the volume specified by path at the given mount_point.
int ensure_path_mounted_at(const char* path, const char* mount_point) {
- Volume* v = volume_for_path(path);
- if (v == NULL) {
- LOG(ERROR) << "unknown volume for path [" << path << "]";
- return -1;
- }
- if (strcmp(v->fs_type, "ramdisk") == 0) {
- // the ramdisk is always mounted.
- return 0;
- }
+ Volume* v = volume_for_path(path);
+ if (v == nullptr) {
+ LOG(ERROR) << "unknown volume for path [" << path << "]";
+ return -1;
+ }
+ if (strcmp(v->fs_type, "ramdisk") == 0) {
+ // The ramdisk is always mounted.
+ return 0;
+ }
- if (!scan_mounted_volumes()) {
- LOG(ERROR) << "failed to scan mounted volumes";
- return -1;
- }
+ if (!scan_mounted_volumes()) {
+ LOG(ERROR) << "Failed to scan mounted volumes";
+ return -1;
+ }
- if (!mount_point) {
- mount_point = v->mount_point;
- }
+ if (!mount_point) {
+ mount_point = v->mount_point;
+ }
- MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point);
- if (mv) {
- // volume is already mounted
- return 0;
+ const MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point);
+ if (mv != nullptr) {
+ // Volume is already mounted.
+ return 0;
+ }
+
+ mkdir(mount_point, 0755); // in case it doesn't already exist
+
+ if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "squashfs") == 0 ||
+ strcmp(v->fs_type, "vfat") == 0) {
+ int result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
+ if (result == -1 && fs_mgr_is_formattable(v)) {
+ PLOG(ERROR) << "Failed to mount " << mount_point << "; formatting";
+ bool crypt_footer = fs_mgr_is_encryptable(v) && !strcmp(v->key_loc, "footer");
+ if (fs_mgr_do_format(v, crypt_footer) == 0) {
+ result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
+ } else {
+ PLOG(ERROR) << "Failed to format " << mount_point;
+ return -1;
+ }
}
- mkdir(mount_point, 0755); // in case it doesn't already exist
-
- if (strcmp(v->fs_type, "ext4") == 0 ||
- strcmp(v->fs_type, "squashfs") == 0 ||
- strcmp(v->fs_type, "vfat") == 0) {
- int result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
- if (result == -1 && fs_mgr_is_formattable(v)) {
- LOG(ERROR) << "failed to mount " << mount_point << " (" << strerror(errno)
- << ") , formatting.....";
- bool crypt_footer = fs_mgr_is_encryptable(v) && !strcmp(v->key_loc, "footer");
- if (fs_mgr_do_format(v, crypt_footer) == 0) {
- result = mount(v->blk_device, mount_point, v->fs_type, v->flags, v->fs_options);
- } else {
- PLOG(ERROR) << "failed to format " << mount_point;
- return -1;
- }
- }
-
- if (result == -1) {
- PLOG(ERROR) << "failed to mount " << mount_point;
- return -1;
- }
- return 0;
+ if (result == -1) {
+ PLOG(ERROR) << "Failed to mount " << mount_point;
+ return -1;
}
+ return 0;
+ }
- LOG(ERROR) << "unknown fs_type \"" << v->fs_type << "\" for " << mount_point;
- return -1;
+ LOG(ERROR) << "unknown fs_type \"" << v->fs_type << "\" for " << mount_point;
+ return -1;
}
int ensure_path_mounted(const char* path) {
- // Mount at the default mount point.
- return ensure_path_mounted_at(path, nullptr);
+ // Mount at the default mount point.
+ return ensure_path_mounted_at(path, nullptr);
}
int ensure_path_unmounted(const char* path) {
- Volume* v = volume_for_path(path);
- if (v == NULL) {
- LOG(ERROR) << "unknown volume for path [" << path << "]";
- return -1;
- }
- if (strcmp(v->fs_type, "ramdisk") == 0) {
- // the ramdisk is always mounted; you can't unmount it.
- return -1;
- }
+ const Volume* v = volume_for_path(path);
+ if (v == nullptr) {
+ LOG(ERROR) << "unknown volume for path [" << path << "]";
+ return -1;
+ }
+ if (strcmp(v->fs_type, "ramdisk") == 0) {
+ // The ramdisk is always mounted; you can't unmount it.
+ return -1;
+ }
- if (!scan_mounted_volumes()) {
- LOG(ERROR) << "failed to scan mounted volumes";
- return -1;
- }
+ if (!scan_mounted_volumes()) {
+ LOG(ERROR) << "Failed to scan mounted volumes";
+ return -1;
+ }
- MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point);
- if (mv == NULL) {
- // volume is already unmounted
- return 0;
- }
+ MountedVolume* mv = find_mounted_volume_by_mount_point(v->mount_point);
+ if (mv == nullptr) {
+ // Volume is already unmounted.
+ return 0;
+ }
- return unmount_mounted_volume(mv);
+ return unmount_mounted_volume(mv);
}
-static int exec_cmd(const char* path, char* const argv[]) {
- int status;
- pid_t child;
- if ((child = vfork()) == 0) {
- execv(path, argv);
- _exit(EXIT_FAILURE);
- }
- waitpid(child, &status, 0);
- if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
- LOG(ERROR) << path << " failed with status " << WEXITSTATUS(status);
- }
- return WEXITSTATUS(status);
+static int exec_cmd(const std::vector<std::string>& args) {
+ CHECK_NE(static_cast<size_t>(0), args.size());
+
+ std::vector<char*> argv(args.size());
+ std::transform(args.cbegin(), args.cend(), argv.begin(),
+ [](const std::string& arg) { return const_cast<char*>(arg.c_str()); });
+ argv.push_back(nullptr);
+
+ pid_t child;
+ if ((child = vfork()) == 0) {
+ execv(argv[0], argv.data());
+ _exit(EXIT_FAILURE);
+ }
+
+ int status;
+ waitpid(child, &status, 0);
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ LOG(ERROR) << args[0] << " failed with status " << WEXITSTATUS(status);
+ }
+ return WEXITSTATUS(status);
+}
+
+static ssize_t get_file_size(int fd, uint64_t reserve_len) {
+ struct stat buf;
+ int ret = fstat(fd, &buf);
+ if (ret) return 0;
+
+ ssize_t computed_size;
+ if (S_ISREG(buf.st_mode)) {
+ computed_size = buf.st_size - reserve_len;
+ } else if (S_ISBLK(buf.st_mode)) {
+ computed_size = get_block_device_size(fd) - reserve_len;
+ } else {
+ computed_size = 0;
+ }
+
+ return computed_size;
}
int format_volume(const char* volume, const char* directory) {
- Volume* v = volume_for_path(volume);
- if (v == NULL) {
- LOG(ERROR) << "unknown volume \"" << volume << "\"";
- return -1;
+ const Volume* v = volume_for_path(volume);
+ if (v == nullptr) {
+ LOG(ERROR) << "unknown volume \"" << volume << "\"";
+ return -1;
+ }
+ if (strcmp(v->fs_type, "ramdisk") == 0) {
+ LOG(ERROR) << "can't format_volume \"" << volume << "\"";
+ return -1;
+ }
+ if (strcmp(v->mount_point, volume) != 0) {
+ LOG(ERROR) << "can't give path \"" << volume << "\" to format_volume";
+ return -1;
+ }
+ if (ensure_path_unmounted(volume) != 0) {
+ LOG(ERROR) << "format_volume: Failed to unmount \"" << v->mount_point << "\"";
+ return -1;
+ }
+ if (strcmp(v->fs_type, "ext4") != 0 && strcmp(v->fs_type, "f2fs") != 0) {
+ LOG(ERROR) << "format_volume: fs_type \"" << v->fs_type << "\" unsupported";
+ return -1;
+ }
+
+ // If there's a key_loc that looks like a path, it should be a block device for storing encryption
+ // metadata. Wipe it too.
+ if (v->key_loc != nullptr && v->key_loc[0] == '/') {
+ LOG(INFO) << "Wiping " << v->key_loc;
+ int fd = open(v->key_loc, O_WRONLY | O_CREAT, 0644);
+ if (fd == -1) {
+ PLOG(ERROR) << "format_volume: Failed to open " << v->key_loc;
+ return -1;
}
- if (strcmp(v->fs_type, "ramdisk") == 0) {
- // you can't format the ramdisk.
- LOG(ERROR) << "can't format_volume \"" << volume << "\"";
- return -1;
+ wipe_block_device(fd, get_file_size(fd));
+ close(fd);
+ }
+
+ ssize_t length = 0;
+ if (v->length != 0) {
+ length = v->length;
+ } else if (v->key_loc != nullptr && strcmp(v->key_loc, "footer") == 0) {
+ android::base::unique_fd fd(open(v->blk_device, O_RDONLY));
+ if (fd == -1) {
+ PLOG(ERROR) << "get_file_size: failed to open " << v->blk_device;
+ return -1;
}
- if (strcmp(v->mount_point, volume) != 0) {
- LOG(ERROR) << "can't give path \"" << volume << "\" to format_volume";
- return -1;
+ length = get_file_size(fd.get(), CRYPT_FOOTER_OFFSET);
+ if (length <= 0) {
+ LOG(ERROR) << "get_file_size: invalid size " << length << " for " << v->blk_device;
+ return -1;
}
+ }
- if (ensure_path_unmounted(volume) != 0) {
- LOG(ERROR) << "format_volume failed to unmount \"" << v->mount_point << "\"";
- return -1;
+ if (strcmp(v->fs_type, "ext4") == 0) {
+ static constexpr int kBlockSize = 4096;
+ std::vector<std::string> mke2fs_args = {
+ "/sbin/mke2fs_static", "-F", "-t", "ext4", "-b", std::to_string(kBlockSize),
+ };
+
+ int raid_stride = v->logical_blk_size / kBlockSize;
+ int raid_stripe_width = v->erase_blk_size / kBlockSize;
+ // stride should be the max of 8KB and logical block size
+ if (v->logical_blk_size != 0 && v->logical_blk_size < 8192) {
+ raid_stride = 8192 / kBlockSize;
+ }
+ if (v->erase_blk_size != 0 && v->logical_blk_size != 0) {
+ mke2fs_args.push_back("-E");
+ mke2fs_args.push_back(
+ android::base::StringPrintf("stride=%d,stripe-width=%d", raid_stride, raid_stripe_width));
+ }
+ mke2fs_args.push_back(v->blk_device);
+ if (length != 0) {
+ mke2fs_args.push_back(std::to_string(length / kBlockSize));
}
- if (strcmp(v->fs_type, "ext4") == 0 || strcmp(v->fs_type, "f2fs") == 0) {
- // if there's a key_loc that looks like a path, it should be a
- // block device for storing encryption metadata. wipe it too.
- if (v->key_loc != NULL && v->key_loc[0] == '/') {
- LOG(INFO) << "wiping " << v->key_loc;
- int fd = open(v->key_loc, O_WRONLY | O_CREAT, 0644);
- if (fd < 0) {
- LOG(ERROR) << "format_volume: failed to open " << v->key_loc;
- return -1;
- }
- wipe_block_device(fd, get_file_size(fd));
- close(fd);
- }
-
- ssize_t length = 0;
- if (v->length != 0) {
- length = v->length;
- } else if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0) {
- length = -CRYPT_FOOTER_OFFSET;
- }
- int result;
- if (strcmp(v->fs_type, "ext4") == 0) {
- if (v->erase_blk_size != 0 && v->logical_blk_size != 0) {
- result = make_ext4fs_directory_align(v->blk_device, length, volume, sehandle,
- directory, v->erase_blk_size, v->logical_blk_size);
- } else {
- result = make_ext4fs_directory(v->blk_device, length, volume, sehandle, directory);
- }
- } else { /* Has to be f2fs because we checked earlier. */
- if (v->key_loc != NULL && strcmp(v->key_loc, "footer") == 0 && length < 0) {
- LOG(ERROR) << "format_volume: crypt footer + negative length (" << length
- << ") not supported on " << v->fs_type;
- return -1;
- }
- if (length < 0) {
- LOG(ERROR) << "format_volume: negative length (" << length
- << ") not supported on " << v->fs_type;
- return -1;
- }
- char *num_sectors = nullptr;
- if (length >= 512 && asprintf(&num_sectors, "%zd", length / 512) <= 0) {
- LOG(ERROR) << "format_volume: failed to create " << v->fs_type
- << " command for " << v->blk_device;
- return -1;
- }
- const char *f2fs_path = "/sbin/mkfs.f2fs";
- const char* const f2fs_argv[] = {"mkfs.f2fs", "-t", "-d1", v->blk_device, num_sectors, nullptr};
-
- result = exec_cmd(f2fs_path, (char* const*)f2fs_argv);
- free(num_sectors);
- }
- if (result != 0) {
- PLOG(ERROR) << "format_volume: make " << v->fs_type << " failed on " << v->blk_device;
- return -1;
- }
- return 0;
+ int result = exec_cmd(mke2fs_args);
+ if (result == 0 && directory != nullptr) {
+ std::vector<std::string> e2fsdroid_args = {
+ "/sbin/e2fsdroid_static",
+ "-e",
+ "-f",
+ directory,
+ "-a",
+ volume,
+ v->blk_device,
+ };
+ result = exec_cmd(e2fsdroid_args);
}
- LOG(ERROR) << "format_volume: fs_type \"" << v->fs_type << "\" unsupported";
+ if (result != 0) {
+ PLOG(ERROR) << "format_volume: Failed to make ext4 on " << v->blk_device;
+ return -1;
+ }
+ return 0;
+ }
+
+ // Has to be f2fs because we checked earlier.
+ std::vector<std::string> f2fs_args = { "/sbin/mkfs.f2fs", "-t", "-d1", v->blk_device };
+ if (length >= 512) {
+ f2fs_args.push_back(std::to_string(length / 512));
+ }
+
+ int result = exec_cmd(f2fs_args);
+ if (result != 0) {
+ PLOG(ERROR) << "format_volume: Failed to make f2fs on " << v->blk_device;
return -1;
+ }
+ return 0;
}
int format_volume(const char* volume) {
- return format_volume(volume, NULL);
+ return format_volume(volume, nullptr);
}
int setup_install_mounts() {
@@ -274,12 +323,12 @@ int setup_install_mounts() {
if (strcmp(v->mount_point, "/tmp") == 0 || strcmp(v->mount_point, "/cache") == 0) {
if (ensure_path_mounted(v->mount_point) != 0) {
- LOG(ERROR) << "failed to mount " << v->mount_point;
+ LOG(ERROR) << "Failed to mount " << v->mount_point;
return -1;
}
} else {
if (ensure_path_unmounted(v->mount_point) != 0) {
- LOG(ERROR) << "failed to unmount " << v->mount_point;
+ LOG(ERROR) << "Failed to unmount " << v->mount_point;
return -1;
}
}
diff --git a/screen_ui.cpp b/screen_ui.cpp
index 8f792f162..5c93b6672 100644
--- a/screen_ui.cpp
+++ b/screen_ui.cpp
@@ -53,6 +53,7 @@ static double now() {
ScreenRecoveryUI::ScreenRecoveryUI()
: kMarginWidth(RECOVERY_UI_MARGIN_WIDTH),
kMarginHeight(RECOVERY_UI_MARGIN_HEIGHT),
+ kAnimationFps(RECOVERY_UI_ANIMATION_FPS),
density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f),
currentIcon(NONE),
progressBarType(EMPTY),
@@ -77,7 +78,6 @@ ScreenRecoveryUI::ScreenRecoveryUI()
loop_frames(0),
current_frame(0),
intro_done(false),
- animation_fps(30), // TODO: there's currently no way to infer this.
stage(-1),
max_stage(-1),
updateMutex(PTHREAD_MUTEX_INITIALIZER) {}
@@ -278,6 +278,34 @@ int ScreenRecoveryUI::DrawTextLines(int x, int y, const char* const* lines) cons
return offset;
}
+int ScreenRecoveryUI::DrawWrappedTextLines(int x, int y, const char* const* lines) const {
+ int offset = 0;
+ for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) {
+ // The line will be wrapped if it exceeds text_cols_.
+ std::string line(lines[i]);
+ size_t next_start = 0;
+ while (next_start < line.size()) {
+ std::string sub = line.substr(next_start, text_cols_ + 1);
+ if (sub.size() <= text_cols_) {
+ next_start += sub.size();
+ } else {
+ // Line too long and must be wrapped to text_cols_ columns.
+ size_t last_space = sub.find_last_of(" \t\n");
+ if (last_space == std::string::npos) {
+ // No space found, just draw as much as we can
+ sub.resize(text_cols_);
+ next_start += text_cols_;
+ } else {
+ sub.resize(last_space);
+ next_start += last_space + 1;
+ }
+ }
+ offset += DrawTextLine(x, y + offset, sub.c_str(), false);
+ }
+ }
+ return offset;
+}
+
static const char* REGULAR_HELP[] = {
"Use volume up/down and power.",
NULL
@@ -316,7 +344,8 @@ void ScreenRecoveryUI::draw_screen_locked() {
y += DrawTextLines(x, y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP);
SetColor(HEADER);
- y += DrawTextLines(x, y, menu_headers_);
+ // Ignore kMenuIndent, which is not taken into account by text_cols_.
+ y += DrawWrappedTextLines(kMarginWidth, y, menu_headers_);
SetColor(MENU);
y += DrawHorizontalRule(y) + 4;
@@ -375,7 +404,7 @@ void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) {
}
void ScreenRecoveryUI::ProgressThreadLoop() {
- double interval = 1.0 / animation_fps;
+ double interval = 1.0 / kAnimationFps;
while (true) {
double start = now();
pthread_mutex_lock(&updateMutex);
diff --git a/screen_ui.h b/screen_ui.h
index 8402fac00..62dda7558 100644
--- a/screen_ui.h
+++ b/screen_ui.h
@@ -84,6 +84,9 @@ class ScreenRecoveryUI : public RecoveryUI {
const int kMarginWidth;
const int kMarginHeight;
+ // Number of frames per sec (default: 30) for both parts of the animation.
+ const int kAnimationFps;
+
// The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi.
const float density_;
@@ -141,9 +144,6 @@ class ScreenRecoveryUI : public RecoveryUI {
size_t current_frame;
bool intro_done;
- // Number of frames per sec (default: 30) for both parts of the animation.
- int animation_fps;
-
int stage, max_stage;
int char_width_;
@@ -187,6 +187,9 @@ class ScreenRecoveryUI : public RecoveryUI {
virtual int DrawTextLine(int x, int y, const char* line, bool bold) const;
// Draws multiple text lines. Returns the offset it should be moving along Y-axis.
int DrawTextLines(int x, int y, const char* const* lines) const;
+ // Similar to DrawTextLines() to draw multiple text lines, but additionally wraps long lines.
+ // Returns the offset it should be moving along Y-axis.
+ int DrawWrappedTextLines(int x, int y, const char* const* lines) const;
};
#endif // RECOVERY_UI_H
diff --git a/tests/Android.mk b/tests/Android.mk
index 8b1dc1099..f2497b8b3 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -111,7 +111,8 @@ LOCAL_SRC_FILES := \
component/update_verifier_test.cpp \
component/verifier_test.cpp
-LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_SHARED_LIBRARIES := \
+ libhidlbase
tune2fs_static_libraries := \
libext2_com_err \
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
index 968196fc0..7bb496066 100644
--- a/tests/component/install_test.cpp
+++ b/tests/component/install_test.cpp
@@ -19,6 +19,7 @@
#include <sys/types.h>
#include <unistd.h>
+#include <algorithm>
#include <string>
#include <vector>
@@ -198,8 +199,8 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml
CloseArchive(zip);
}
-TEST(InstallTest, update_binary_command_smoke) {
#ifdef AB_OTA_UPDATER
+static void VerifyAbUpdateBinaryCommand(const std::string& serialno, bool success = true) {
TemporaryFile temp_file;
FILE* zip_file = fdopen(temp_file.fd, "w");
ZipWriter writer(zip_file);
@@ -215,11 +216,13 @@ TEST(InstallTest, update_binary_command_smoke) {
ASSERT_NE("", device);
std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
ASSERT_NE("", timestamp);
- std::string metadata = android::base::Join(
- std::vector<std::string>{
- "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
- },
- "\n");
+
+ std::vector<std::string> meta{ "ota-type=AB", "pre-device=" + device,
+ "post-timestamp=" + timestamp };
+ if (!serialno.empty()) {
+ meta.push_back("serialno=" + serialno);
+ }
+ std::string metadata = android::base::Join(meta, "\n");
ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
ASSERT_EQ(0, writer.FinishEntry());
ASSERT_EQ(0, writer.Finish());
@@ -234,14 +237,25 @@ TEST(InstallTest, update_binary_command_smoke) {
std::string package = "/path/to/update.zip";
std::string binary_path = "/sbin/update_engine_sideload";
std::vector<std::string> cmd;
- ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
- ASSERT_EQ(5U, cmd.size());
- ASSERT_EQ(binary_path, cmd[0]);
- ASSERT_EQ("--payload=file://" + package, cmd[1]);
- ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
- ASSERT_EQ("--headers=" + properties, cmd[3]);
- ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
+ if (success) {
+ ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
+ ASSERT_EQ(5U, cmd.size());
+ ASSERT_EQ(binary_path, cmd[0]);
+ ASSERT_EQ("--payload=file://" + package, cmd[1]);
+ ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
+ ASSERT_EQ("--headers=" + properties, cmd[3]);
+ ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
+ } else {
+ ASSERT_EQ(INSTALL_ERROR, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
+ }
CloseArchive(zip);
+}
+#endif // AB_OTA_UPDATER
+
+TEST(InstallTest, update_binary_command_smoke) {
+#ifdef AB_OTA_UPDATER
+ // Empty serialno will pass the verification.
+ VerifyAbUpdateBinaryCommand({});
#else
TemporaryFile temp_file;
FILE* zip_file = fdopen(temp_file.fd, "w");
@@ -340,3 +354,34 @@ TEST(InstallTest, update_binary_command_invalid) {
CloseArchive(zip);
#endif // AB_OTA_UPDATER
}
+
+#ifdef AB_OTA_UPDATER
+TEST(InstallTest, update_binary_command_multiple_serialno) {
+ std::string serialno = android::base::GetProperty("ro.serialno", "");
+ ASSERT_NE("", serialno);
+
+ // Single matching serialno will pass the verification.
+ VerifyAbUpdateBinaryCommand(serialno);
+
+ static constexpr char alphabet[] =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ auto generator = []() { return alphabet[rand() % (sizeof(alphabet) - 1)]; };
+
+ // Generate 900 random serial numbers.
+ std::string random_serial;
+ for (size_t i = 0; i < 900; i++) {
+ generate_n(back_inserter(random_serial), serialno.size(), generator);
+ random_serial.append("|");
+ }
+ // Random serialnos should fail the verification.
+ VerifyAbUpdateBinaryCommand(random_serial, false);
+
+ std::string long_serial = random_serial + serialno + "|";
+ for (size_t i = 0; i < 99; i++) {
+ generate_n(back_inserter(long_serial), serialno.size(), generator);
+ long_serial.append("|");
+ }
+ // String with the matching serialno should pass the verification.
+ VerifyAbUpdateBinaryCommand(long_serial);
+}
+#endif // AB_OTA_UPDATER
diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp
index 5fc7ef63f..b04e1185e 100644
--- a/tests/component/update_verifier_test.cpp
+++ b/tests/component/update_verifier_test.cpp
@@ -81,3 +81,16 @@ TEST_F(UpdateVerifierTest, verify_image_malformed_care_map) {
ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
ASSERT_FALSE(verify_image(temp_file.path));
}
+
+TEST_F(UpdateVerifierTest, verify_image_legacy_care_map) {
+ // This test relies on dm-verity support.
+ if (!verity_supported) {
+ GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+ return;
+ }
+
+ TemporaryFile temp_file;
+ std::string content = "/dev/block/bootdevice/by-name/system\n2,1,0";
+ ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+ ASSERT_TRUE(verify_image(temp_file.path));
+}
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
index 357a39ef7..6c341c111 100644
--- a/tests/component/updater_test.cpp
+++ b/tests/component/updater_test.cpp
@@ -485,7 +485,7 @@ TEST_F(UpdaterTest, block_image_update) {
UpdaterInfo updater_info;
updater_info.package_zip = handle;
TemporaryFile temp_pipe;
- updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
+ updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe");
updater_info.package_zip_addr = map.addr;
updater_info.package_zip_len = map.length;
@@ -561,7 +561,7 @@ TEST_F(UpdaterTest, new_data_short_write) {
UpdaterInfo updater_info;
updater_info.package_zip = handle;
TemporaryFile temp_pipe;
- updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
+ updater_info.cmd_pipe = fopen(temp_pipe.path, "wbe");
updater_info.package_zip_addr = map.addr;
updater_info.package_zip_len = map.length;
@@ -592,10 +592,10 @@ TEST_F(UpdaterTest, brotli_new_data) {
ASSERT_EQ(0, zip_writer.StartEntry("new.dat.br", 0));
auto generator = []() { return rand() % 128; };
- // Generate 2048 blocks of random data.
+ // Generate 100 blocks of random data.
std::string brotli_new_data;
- brotli_new_data.reserve(4096 * 2048);
- generate_n(back_inserter(brotli_new_data), 4096 * 2048, generator);
+ brotli_new_data.reserve(4096 * 100);
+ generate_n(back_inserter(brotli_new_data), 4096 * 100, generator);
size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size());
std::vector<uint8_t> encoded_data(encoded_size);
@@ -609,8 +609,19 @@ TEST_F(UpdaterTest, brotli_new_data) {
ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
ASSERT_EQ(0, zip_writer.FinishEntry());
+ // Write a few small chunks of new data, then a large chunk, and finally a few small chunks.
+ // This helps us to catch potential short writes.
std::vector<std::string> transfer_list = {
- "4", "2048", "0", "0", "new 4,0,512,512,1024", "new 2,1024,2048",
+ "4",
+ "100",
+ "0",
+ "0",
+ "new 2,0,1",
+ "new 2,1,2",
+ "new 4,2,50,50,97",
+ "new 2,97,98",
+ "new 2,98,99",
+ "new 2,99,100",
};
ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
std::string commands = android::base::Join(transfer_list, '\n');
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
index 5338f05c6..e520f5028 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -33,6 +33,8 @@
#include "otautil/SysUtil.h"
#include "verifier.h"
+using namespace std::string_literals;
+
class VerifierTest : public testing::TestWithParam<std::vector<std::string>> {
protected:
void SetUp() override {
@@ -115,6 +117,21 @@ TEST(VerifierTest, load_keys_invalid_keys) {
ASSERT_FALSE(load_keys(key_file5.path, certs));
}
+TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) {
+ std::string testkey_v3;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
+
+ TemporaryFile key_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file.path));
+ std::vector<Certificate> certs;
+ ASSERT_TRUE(load_keys(key_file.path, certs));
+
+ // Signature start is 65535 (0xffff) while comment size is 0 (Bug: 31914369).
+ std::string package = "\x50\x4b\x05\x06"s + std::string(12, '\0') + "\xff\xff\xff\xff\x00\x00"s;
+ ASSERT_EQ(VERIFY_FAILURE, verify_file(reinterpret_cast<const unsigned char*>(package.data()),
+ package.size(), certs));
+}
+
TEST(VerifierTest, BadPackage_AlteredFooter) {
std::string testkey_v3;
ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp
index d36dd331e..92c6ef2d4 100644
--- a/tests/manual/recovery_test.cpp
+++ b/tests/manual/recovery_test.cpp
@@ -141,7 +141,7 @@ class ResourceTest : public testing::TestWithParam<std::string> {
// under recovery.
void SetUp() override {
std::string file_path = GetParam();
- fp = fopen(file_path.c_str(), "rb");
+ fp = fopen(file_path.c_str(), "rbe");
ASSERT_NE(nullptr, fp);
unsigned char header[8];
diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp
index 5e2ae4fb5..7f85d13ea 100644
--- a/tests/unit/dirutil_test.cpp
+++ b/tests/unit/dirutil_test.cpp
@@ -26,23 +26,23 @@
TEST(DirUtilTest, create_invalid) {
// Requesting to create an empty dir is invalid.
- ASSERT_EQ(-1, dirCreateHierarchy("", 0755, nullptr, false, nullptr));
+ ASSERT_EQ(-1, mkdir_recursively("", 0755, false, nullptr));
ASSERT_EQ(ENOENT, errno);
// Requesting to strip the name with no slash present.
- ASSERT_EQ(-1, dirCreateHierarchy("abc", 0755, nullptr, true, nullptr));
+ ASSERT_EQ(-1, mkdir_recursively("abc", 0755, true, nullptr));
ASSERT_EQ(ENOENT, errno);
// Creating a dir that already exists.
TemporaryDir td;
- ASSERT_EQ(0, dirCreateHierarchy(td.path, 0755, nullptr, false, nullptr));
+ ASSERT_EQ(0, mkdir_recursively(td.path, 0755, false, nullptr));
// "///" is a valid dir.
- ASSERT_EQ(0, dirCreateHierarchy("///", 0755, nullptr, false, nullptr));
+ ASSERT_EQ(0, mkdir_recursively("///", 0755, false, nullptr));
// Request to create a dir, but a file with the same name already exists.
TemporaryFile tf;
- ASSERT_EQ(-1, dirCreateHierarchy(tf.path, 0755, nullptr, false, nullptr));
+ ASSERT_EQ(-1, mkdir_recursively(tf.path, 0755, false, nullptr));
ASSERT_EQ(ENOTDIR, errno);
}
@@ -51,7 +51,7 @@ TEST(DirUtilTest, create_smoke) {
std::string prefix(td.path);
std::string path = prefix + "/a/b";
constexpr mode_t mode = 0755;
- ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), mode, nullptr, false, nullptr));
+ ASSERT_EQ(0, mkdir_recursively(path, mode, false, nullptr));
// Verify.
struct stat sb;
@@ -69,7 +69,7 @@ TEST(DirUtilTest, create_strip_filename) {
TemporaryDir td;
std::string prefix(td.path);
std::string path = prefix + "/a/b";
- ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), 0755, nullptr, true, nullptr));
+ ASSERT_EQ(0, mkdir_recursively(path, 0755, true, nullptr));
// Verify that "../a" exists but not "../a/b".
struct stat sb;
@@ -83,31 +83,21 @@ TEST(DirUtilTest, create_strip_filename) {
ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
}
-TEST(DirUtilTest, create_mode_and_timestamp) {
+TEST(DirUtilTest, create_mode) {
TemporaryDir td;
std::string prefix(td.path);
std::string path = prefix + "/a/b";
- // Set the timestamp to 8/1/2008.
- constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };
constexpr mode_t mode = 0751;
- ASSERT_EQ(0, dirCreateHierarchy(path.c_str(), mode, &timestamp, false, nullptr));
+ ASSERT_EQ(0, mkdir_recursively(path, mode, false, nullptr));
- // Verify the mode and timestamp for "../a/b".
+ // Verify the mode for "../a/b".
struct stat sb;
ASSERT_EQ(0, stat(path.c_str(), &sb)) << strerror(errno);
ASSERT_TRUE(S_ISDIR(sb.st_mode));
constexpr mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO;
ASSERT_EQ(mode, sb.st_mode & mask);
- timespec time;
- time.tv_sec = 1217592000;
- time.tv_nsec = 0;
-
- ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
- ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
-
- // Verify the mode for "../a". Note that the timestamp for intermediate directories (e.g. "../a")
- // may not be 'timestamp' according to the current implementation.
+ // Verify the mode for "../a".
ASSERT_EQ(0, stat((prefix + "/a").c_str(), &sb)) << strerror(errno);
ASSERT_TRUE(S_ISDIR(sb.st_mode));
ASSERT_EQ(mode, sb.st_mode & mask);
@@ -116,35 +106,3 @@ TEST(DirUtilTest, create_mode_and_timestamp) {
ASSERT_EQ(0, rmdir((prefix + "/a/b").c_str()));
ASSERT_EQ(0, rmdir((prefix + "/a").c_str()));
}
-
-TEST(DirUtilTest, unlink_invalid) {
- // File doesn't exist.
- ASSERT_EQ(-1, dirUnlinkHierarchy("doesntexist"));
-
- // Nonexistent directory.
- TemporaryDir td;
- std::string path(td.path);
- ASSERT_EQ(-1, dirUnlinkHierarchy((path + "/a").c_str()));
- ASSERT_EQ(ENOENT, errno);
-}
-
-TEST(DirUtilTest, unlink_smoke) {
- // Unlink a file.
- TemporaryFile tf;
- ASSERT_EQ(0, dirUnlinkHierarchy(tf.path));
- ASSERT_EQ(-1, access(tf.path, F_OK));
-
- TemporaryDir td;
- std::string path(td.path);
- constexpr mode_t mode = 0700;
- ASSERT_EQ(0, mkdir((path + "/a").c_str(), mode));
- ASSERT_EQ(0, mkdir((path + "/a/b").c_str(), mode));
- ASSERT_EQ(0, mkdir((path + "/a/b/c").c_str(), mode));
- ASSERT_EQ(0, mkdir((path + "/a/d").c_str(), mode));
-
- // Remove "../a" recursively.
- ASSERT_EQ(0, dirUnlinkHierarchy((path + "/a").c_str()));
-
- // Verify it's gone.
- ASSERT_EQ(-1, access((path + "/a").c_str(), F_OK));
-}
diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp
index 3c6d77ef5..15bcec855 100644
--- a/tests/unit/rangeset_test.cpp
+++ b/tests/unit/rangeset_test.cpp
@@ -21,7 +21,7 @@
#include <gtest/gtest.h>
-#include "updater/rangeset.h"
+#include "rangeset.h"
TEST(RangeSetTest, Parse_smoke) {
RangeSet rs = RangeSet::Parse("2,1,10");
@@ -110,3 +110,50 @@ TEST(RangeSetTest, iterators) {
}
ASSERT_EQ((std::vector<Range>{ Range{ 8, 10 }, Range{ 1, 5 } }), ranges);
}
+
+TEST(RangeSetTest, tostring) {
+ ASSERT_EQ("2,1,6", RangeSet::Parse("2,1,6").ToString());
+ ASSERT_EQ("4,1,5,8,10", RangeSet::Parse("4,1,5,8,10").ToString());
+ ASSERT_EQ("6,1,3,4,6,15,22", RangeSet::Parse("6,1,3,4,6,15,22").ToString());
+}
+
+TEST(SortedRangeSetTest, insertion) {
+ SortedRangeSet rs({ { 2, 3 }, { 4, 6 }, { 8, 14 } });
+ rs.Insert({ 1, 2 });
+ ASSERT_EQ(SortedRangeSet({ { 1, 3 }, { 4, 6 }, { 8, 14 } }), rs);
+ ASSERT_EQ(static_cast<size_t>(10), rs.blocks());
+ rs.Insert({ 3, 5 });
+ ASSERT_EQ(SortedRangeSet({ { 1, 6 }, { 8, 14 } }), rs);
+ ASSERT_EQ(static_cast<size_t>(11), rs.blocks());
+
+ SortedRangeSet r1({ { 20, 22 }, { 15, 18 } });
+ rs.Insert(r1);
+ ASSERT_EQ(SortedRangeSet({ { 1, 6 }, { 8, 14 }, { 15, 18 }, { 20, 22 } }), rs);
+ ASSERT_EQ(static_cast<size_t>(16), rs.blocks());
+
+ SortedRangeSet r2({ { 2, 7 }, { 15, 21 }, { 20, 25 } });
+ rs.Insert(r2);
+ ASSERT_EQ(SortedRangeSet({ { 1, 7 }, { 8, 14 }, { 15, 25 } }), rs);
+ ASSERT_EQ(static_cast<size_t>(22), rs.blocks());
+}
+
+TEST(SortedRangeSetTest, file_range) {
+ SortedRangeSet rs;
+ rs.Insert(4096, 4096);
+ ASSERT_EQ(SortedRangeSet({ { 1, 2 } }), rs);
+ // insert block 2-9
+ rs.Insert(4096 * 3 - 1, 4096 * 7);
+ ASSERT_EQ(SortedRangeSet({ { 1, 10 } }), rs);
+ // insert block 15-19
+ rs.Insert(4096 * 15 + 1, 4096 * 4);
+ ASSERT_EQ(SortedRangeSet({ { 1, 10 }, { 15, 20 } }), rs);
+
+ // rs overlaps block 2-2
+ ASSERT_TRUE(rs.Overlaps(4096 * 2 - 1, 10));
+ ASSERT_FALSE(rs.Overlaps(4096 * 10, 4096 * 5));
+
+ ASSERT_EQ(static_cast<size_t>(10), rs.GetOffsetInRangeSet(4106));
+ ASSERT_EQ(static_cast<size_t>(40970), rs.GetOffsetInRangeSet(4096 * 16 + 10));
+ // block#10 not in range.
+ ASSERT_EXIT(rs.GetOffsetInRangeSet(40970), ::testing::KilledBySignal(SIGABRT), "");
+} \ No newline at end of file
diff --git a/tools/recovery_l10n/res/values-az-rAZ/strings.xml b/tools/recovery_l10n/res/values-az/strings.xml
index c6765a9ea..c6765a9ea 100644
--- a/tools/recovery_l10n/res/values-az-rAZ/strings.xml
+++ b/tools/recovery_l10n/res/values-az/strings.xml
diff --git a/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml b/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 000000000..c2d8f2239
--- /dev/null
+++ b/tools/recovery_l10n/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="recovery_installing" msgid="2013591905463558223">"Ažuriranje sistema se instalira"</string>
+ <string name="recovery_erasing" msgid="7334826894904037088">"Briše se"</string>
+ <string name="recovery_no_command" msgid="4465476568623024327">"Nema komande"</string>
+ <string name="recovery_error" msgid="5748178989622716736">"Greška!"</string>
+ <string name="recovery_installing_security" msgid="9184031299717114342">"Instalira se bezbednosno ažuriranje"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-be/strings.xml b/tools/recovery_l10n/res/values-be/strings.xml
new file mode 100644
index 000000000..7c0954d31
--- /dev/null
+++ b/tools/recovery_l10n/res/values-be/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="recovery_installing" msgid="2013591905463558223">"Усталёўка абнаўлення сістэмы"</string>
+ <string name="recovery_erasing" msgid="7334826894904037088">"Сціранне"</string>
+ <string name="recovery_no_command" msgid="4465476568623024327">"Няма каманды"</string>
+ <string name="recovery_error" msgid="5748178989622716736">"Памылка"</string>
+ <string name="recovery_installing_security" msgid="9184031299717114342">"Усталёўка абнаўлення сістэмы бяспекі"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-bn-rBD/strings.xml b/tools/recovery_l10n/res/values-bn/strings.xml
index 0a481faf1..0a481faf1 100644
--- a/tools/recovery_l10n/res/values-bn-rBD/strings.xml
+++ b/tools/recovery_l10n/res/values-bn/strings.xml
diff --git a/tools/recovery_l10n/res/values-bs/strings.xml b/tools/recovery_l10n/res/values-bs/strings.xml
new file mode 100644
index 000000000..412cf0276
--- /dev/null
+++ b/tools/recovery_l10n/res/values-bs/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="recovery_installing" msgid="2013591905463558223">"Ažuriranje sistema…"</string>
+ <string name="recovery_erasing" msgid="7334826894904037088">"Brisanje u toku"</string>
+ <string name="recovery_no_command" msgid="4465476568623024327">"Nema komande"</string>
+ <string name="recovery_error" msgid="5748178989622716736">"Greška!"</string>
+ <string name="recovery_installing_security" msgid="9184031299717114342">"Instaliranje sigurnosnog ažuriranja…"</string>
+</resources>
diff --git a/tools/recovery_l10n/res/values-et-rEE/strings.xml b/tools/recovery_l10n/res/values-et/strings.xml
index 072a9ef80..072a9ef80 100644
--- a/tools/recovery_l10n/res/values-et-rEE/strings.xml
+++ b/tools/recovery_l10n/res/values-et/strings.xml
diff --git a/tools/recovery_l10n/res/values-eu-rES/strings.xml b/tools/recovery_l10n/res/values-eu/strings.xml
index 5540469d0..5540469d0 100644
--- a/tools/recovery_l10n/res/values-eu-rES/strings.xml
+++ b/tools/recovery_l10n/res/values-eu/strings.xml
diff --git a/tools/recovery_l10n/res/values-gl-rES/strings.xml b/tools/recovery_l10n/res/values-gl/strings.xml
index 42b2016c2..42b2016c2 100644
--- a/tools/recovery_l10n/res/values-gl-rES/strings.xml
+++ b/tools/recovery_l10n/res/values-gl/strings.xml
diff --git a/tools/recovery_l10n/res/values-gu-rIN/strings.xml b/tools/recovery_l10n/res/values-gu/strings.xml
index 2355a0f4f..2355a0f4f 100644
--- a/tools/recovery_l10n/res/values-gu-rIN/strings.xml
+++ b/tools/recovery_l10n/res/values-gu/strings.xml
diff --git a/tools/recovery_l10n/res/values-hy-rAM/strings.xml b/tools/recovery_l10n/res/values-hy/strings.xml
index 9d62bb763..9d62bb763 100644
--- a/tools/recovery_l10n/res/values-hy-rAM/strings.xml
+++ b/tools/recovery_l10n/res/values-hy/strings.xml
diff --git a/tools/recovery_l10n/res/values-is-rIS/strings.xml b/tools/recovery_l10n/res/values-is/strings.xml
index 5065b6522..5065b6522 100644
--- a/tools/recovery_l10n/res/values-is-rIS/strings.xml
+++ b/tools/recovery_l10n/res/values-is/strings.xml
diff --git a/tools/recovery_l10n/res/values-ka-rGE/strings.xml b/tools/recovery_l10n/res/values-ka/strings.xml
index 6a46b3677..6a46b3677 100644
--- a/tools/recovery_l10n/res/values-ka-rGE/strings.xml
+++ b/tools/recovery_l10n/res/values-ka/strings.xml
diff --git a/tools/recovery_l10n/res/values-kk-rKZ/strings.xml b/tools/recovery_l10n/res/values-kk/strings.xml
index a4bd86e66..a4bd86e66 100644
--- a/tools/recovery_l10n/res/values-kk-rKZ/strings.xml
+++ b/tools/recovery_l10n/res/values-kk/strings.xml
diff --git a/tools/recovery_l10n/res/values-km-rKH/strings.xml b/tools/recovery_l10n/res/values-km/strings.xml
index 313c0f457..313c0f457 100644
--- a/tools/recovery_l10n/res/values-km-rKH/strings.xml
+++ b/tools/recovery_l10n/res/values-km/strings.xml
diff --git a/tools/recovery_l10n/res/values-kn-rIN/strings.xml b/tools/recovery_l10n/res/values-kn/strings.xml
index 5bf6260ee..5bf6260ee 100644
--- a/tools/recovery_l10n/res/values-kn-rIN/strings.xml
+++ b/tools/recovery_l10n/res/values-kn/strings.xml
diff --git a/tools/recovery_l10n/res/values-ky-rKG/strings.xml b/tools/recovery_l10n/res/values-ky/strings.xml
index 0a6bd783a..0a6bd783a 100644
--- a/tools/recovery_l10n/res/values-ky-rKG/strings.xml
+++ b/tools/recovery_l10n/res/values-ky/strings.xml
diff --git a/tools/recovery_l10n/res/values-lo-rLA/strings.xml b/tools/recovery_l10n/res/values-lo/strings.xml
index d3dbb3970..d3dbb3970 100644
--- a/tools/recovery_l10n/res/values-lo-rLA/strings.xml
+++ b/tools/recovery_l10n/res/values-lo/strings.xml
diff --git a/tools/recovery_l10n/res/values-mk-rMK/strings.xml b/tools/recovery_l10n/res/values-mk/strings.xml
index 351459730..351459730 100644
--- a/tools/recovery_l10n/res/values-mk-rMK/strings.xml
+++ b/tools/recovery_l10n/res/values-mk/strings.xml
diff --git a/tools/recovery_l10n/res/values-ml-rIN/strings.xml b/tools/recovery_l10n/res/values-ml/strings.xml
index b506e2530..b506e2530 100644
--- a/tools/recovery_l10n/res/values-ml-rIN/strings.xml
+++ b/tools/recovery_l10n/res/values-ml/strings.xml
diff --git a/tools/recovery_l10n/res/values-mn-rMN/strings.xml b/tools/recovery_l10n/res/values-mn/strings.xml
index e3dd2e90e..e3dd2e90e 100644
--- a/tools/recovery_l10n/res/values-mn-rMN/strings.xml
+++ b/tools/recovery_l10n/res/values-mn/strings.xml
diff --git a/tools/recovery_l10n/res/values-mr-rIN/strings.xml b/tools/recovery_l10n/res/values-mr/strings.xml
index 8cf86f773..8cf86f773 100644
--- a/tools/recovery_l10n/res/values-mr-rIN/strings.xml
+++ b/tools/recovery_l10n/res/values-mr/strings.xml
diff --git a/tools/recovery_l10n/res/values-ms-rMY/strings.xml b/tools/recovery_l10n/res/values-ms/strings.xml
index 0e24ac4e1..0e24ac4e1 100644
--- a/tools/recovery_l10n/res/values-ms-rMY/strings.xml
+++ b/tools/recovery_l10n/res/values-ms/strings.xml
diff --git a/tools/recovery_l10n/res/values-my-rMM/strings.xml b/tools/recovery_l10n/res/values-my/strings.xml
index f13752461..f13752461 100644
--- a/tools/recovery_l10n/res/values-my-rMM/strings.xml
+++ b/tools/recovery_l10n/res/values-my/strings.xml
diff --git a/tools/recovery_l10n/res/values-ne-rNP/strings.xml b/tools/recovery_l10n/res/values-ne/strings.xml
index 1880e807b..1880e807b 100644
--- a/tools/recovery_l10n/res/values-ne-rNP/strings.xml
+++ b/tools/recovery_l10n/res/values-ne/strings.xml
diff --git a/tools/recovery_l10n/res/values-pa-rIN/strings.xml b/tools/recovery_l10n/res/values-pa/strings.xml
index 8564c9c36..8564c9c36 100644
--- a/tools/recovery_l10n/res/values-pa-rIN/strings.xml
+++ b/tools/recovery_l10n/res/values-pa/strings.xml
diff --git a/tools/recovery_l10n/res/values-si-rLK/strings.xml b/tools/recovery_l10n/res/values-si/strings.xml
index 456cdc567..456cdc567 100644
--- a/tools/recovery_l10n/res/values-si-rLK/strings.xml
+++ b/tools/recovery_l10n/res/values-si/strings.xml
diff --git a/tools/recovery_l10n/res/values-sq-rAL/strings.xml b/tools/recovery_l10n/res/values-sq/strings.xml
index 1156931fb..1156931fb 100644
--- a/tools/recovery_l10n/res/values-sq-rAL/strings.xml
+++ b/tools/recovery_l10n/res/values-sq/strings.xml
diff --git a/tools/recovery_l10n/res/values-ta-rIN/strings.xml b/tools/recovery_l10n/res/values-ta/strings.xml
index d49186d8d..d49186d8d 100644
--- a/tools/recovery_l10n/res/values-ta-rIN/strings.xml
+++ b/tools/recovery_l10n/res/values-ta/strings.xml
diff --git a/tools/recovery_l10n/res/values-te-rIN/strings.xml b/tools/recovery_l10n/res/values-te/strings.xml
index cfb02c915..cfb02c915 100644
--- a/tools/recovery_l10n/res/values-te-rIN/strings.xml
+++ b/tools/recovery_l10n/res/values-te/strings.xml
diff --git a/tools/recovery_l10n/res/values-ur-rPK/strings.xml b/tools/recovery_l10n/res/values-ur/strings.xml
index 12e32fbc1..12e32fbc1 100644
--- a/tools/recovery_l10n/res/values-ur-rPK/strings.xml
+++ b/tools/recovery_l10n/res/values-ur/strings.xml
diff --git a/tools/recovery_l10n/res/values-uz-rUZ/strings.xml b/tools/recovery_l10n/res/values-uz/strings.xml
index 2c309d646..2c309d646 100644
--- a/tools/recovery_l10n/res/values-uz-rUZ/strings.xml
+++ b/tools/recovery_l10n/res/values-uz/strings.xml
diff --git a/ui.cpp b/ui.cpp
index 30b42a19a..e80d7ed04 100644
--- a/ui.cpp
+++ b/ui.cpp
@@ -54,6 +54,9 @@ RecoveryUI::RecoveryUI()
rtl_locale_(false),
brightness_normal_(50),
brightness_dimmed_(25),
+ touch_screen_allowed_(false),
+ kTouchLowThreshold(RECOVERY_UI_TOUCH_LOW_THRESHOLD),
+ kTouchHighThreshold(RECOVERY_UI_TOUCH_HIGH_THRESHOLD),
key_queue_len(0),
key_last_down(-1),
key_long_press(false),
@@ -64,6 +67,9 @@ RecoveryUI::RecoveryUI()
has_power_key(false),
has_up_key(false),
has_down_key(false),
+ has_touch_screen(false),
+ touch_slot_(0),
+ is_bootreason_recovery_ui_(false),
screensaver_state_(ScreensaverState::DISABLED) {
pthread_mutex_init(&key_queue_mutex, nullptr);
pthread_cond_init(&key_queue_cond, nullptr);
@@ -77,6 +83,8 @@ void RecoveryUI::OnKeyDetected(int key_code) {
has_down_key = true;
} else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) {
has_up_key = true;
+ } else if (key_code == ABS_MT_POSITION_X || key_code == ABS_MT_POSITION_Y) {
+ has_touch_screen = true;
}
}
@@ -128,10 +136,28 @@ bool RecoveryUI::Init(const std::string& locale) {
// Set up the locale info.
SetLocale(locale);
- ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2));
+ ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2),
+ touch_screen_allowed_);
ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
+ if (touch_screen_allowed_) {
+ ev_iterate_touch_inputs(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1));
+
+ // Parse /proc/cmdline to determine if it's booting into recovery with a bootreason of
+ // "recovery_ui". This specific reason is set by some (wear) bootloaders, to allow an easier way
+ // to turn on text mode. It will only be set if the recovery boot is triggered from fastboot, or
+ // with 'adb reboot recovery'. Note that this applies to all build variants. Otherwise the text
+ // mode will be turned on automatically on debuggable builds, even without a swipe.
+ std::string cmdline;
+ if (android::base::ReadFileToString("/proc/cmdline", &cmdline)) {
+ is_bootreason_recovery_ui_ = cmdline.find("bootreason=recovery_ui") != std::string::npos;
+ } else {
+ // Non-fatal, and won't affect Init() result.
+ PLOG(WARNING) << "Failed to read /proc/cmdline";
+ }
+ }
+
if (!InitScreensaver()) {
LOG(INFO) << "Screensaver disabled";
}
@@ -140,15 +166,91 @@ bool RecoveryUI::Init(const std::string& locale) {
return true;
}
+void RecoveryUI::OnTouchDetected(int dx, int dy) {
+ enum SwipeDirection { UP, DOWN, RIGHT, LEFT } direction;
+
+ // We only consider a valid swipe if:
+ // - the delta along one axis is below kTouchLowThreshold;
+ // - and the delta along the other axis is beyond kTouchHighThreshold.
+ if (abs(dy) < kTouchLowThreshold && abs(dx) > kTouchHighThreshold) {
+ direction = dx < 0 ? SwipeDirection::LEFT : SwipeDirection::RIGHT;
+ } else if (abs(dx) < kTouchLowThreshold && abs(dy) > kTouchHighThreshold) {
+ direction = dy < 0 ? SwipeDirection::UP : SwipeDirection::DOWN;
+ } else {
+ LOG(DEBUG) << "Ignored " << dx << " " << dy << " (low: " << kTouchLowThreshold
+ << ", high: " << kTouchHighThreshold << ")";
+ return;
+ }
+
+ // Allow turning on text mode with any swipe, if bootloader has set a bootreason of recovery_ui.
+ if (is_bootreason_recovery_ui_ && !IsTextVisible()) {
+ ShowText(true);
+ return;
+ }
+
+ LOG(DEBUG) << "Swipe direction=" << direction;
+ switch (direction) {
+ case SwipeDirection::UP:
+ ProcessKey(KEY_UP, 1); // press up key
+ ProcessKey(KEY_UP, 0); // and release it
+ break;
+
+ case SwipeDirection::DOWN:
+ ProcessKey(KEY_DOWN, 1); // press down key
+ ProcessKey(KEY_DOWN, 0); // and release it
+ break;
+
+ case SwipeDirection::LEFT:
+ case SwipeDirection::RIGHT:
+ ProcessKey(KEY_POWER, 1); // press power key
+ ProcessKey(KEY_POWER, 0); // and release it
+ break;
+ };
+}
+
int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
struct input_event ev;
if (ev_get_input(fd, epevents, &ev) == -1) {
return -1;
}
+ // Touch inputs handling.
+ //
+ // We handle the touch inputs by tracking the position changes between initial contacting and
+ // upon lifting. touch_start_X/Y record the initial positions, with touch_finger_down set. Upon
+ // detecting the lift, we unset touch_finger_down and detect a swipe based on position changes.
+ //
+ // Per the doc Multi-touch Protocol at below, there are two protocols.
+ // https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt
+ //
+ // The main difference between the stateless type A protocol and the stateful type B slot protocol
+ // lies in the usage of identifiable contacts to reduce the amount of data sent to userspace. The
+ // slot protocol (i.e. type B) sends ABS_MT_TRACKING_ID with a unique id on initial contact, and
+ // sends ABS_MT_TRACKING_ID -1 upon lifting the contact. Protocol A doesn't send
+ // ABS_MT_TRACKING_ID -1 on lifting, but the driver may additionally report BTN_TOUCH event.
+ //
+ // For protocol A, we rely on BTN_TOUCH to recognize lifting, while for protocol B we look for
+ // ABS_MT_TRACKING_ID being -1.
+ //
+ // Touch input events will only be available if touch_screen_allowed_ is set.
+
if (ev.type == EV_SYN) {
+ if (touch_screen_allowed_ && ev.code == SYN_REPORT) {
+ // There might be multiple SYN_REPORT events. We should only detect a swipe after lifting the
+ // contact.
+ if (touch_finger_down_ && !touch_swiping_) {
+ touch_start_X_ = touch_X_;
+ touch_start_Y_ = touch_Y_;
+ touch_swiping_ = true;
+ } else if (!touch_finger_down_ && touch_swiping_) {
+ touch_swiping_ = false;
+ OnTouchDetected(touch_X_ - touch_start_X_, touch_Y_ - touch_start_Y_);
+ }
+ }
return 0;
- } else if (ev.type == EV_REL) {
+ }
+
+ if (ev.type == EV_REL) {
if (ev.code == REL_Y) {
// accumulate the up or down motion reported by
// the trackball. When it exceeds a threshold
@@ -169,7 +271,48 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) {
rel_sum = 0;
}
+ if (touch_screen_allowed_ && ev.type == EV_ABS) {
+ if (ev.code == ABS_MT_SLOT) {
+ touch_slot_ = ev.value;
+ }
+ // Ignore other fingers.
+ if (touch_slot_ > 0) return 0;
+
+ switch (ev.code) {
+ case ABS_MT_POSITION_X:
+ touch_X_ = ev.value;
+ touch_finger_down_ = true;
+ break;
+
+ case ABS_MT_POSITION_Y:
+ touch_Y_ = ev.value;
+ touch_finger_down_ = true;
+ break;
+
+ case ABS_MT_TRACKING_ID:
+ // Protocol B: -1 marks lifting the contact.
+ if (ev.value < 0) touch_finger_down_ = false;
+ break;
+ }
+ return 0;
+ }
+
if (ev.type == EV_KEY && ev.code <= KEY_MAX) {
+ if (touch_screen_allowed_) {
+ if (ev.code == BTN_TOUCH) {
+ // A BTN_TOUCH with value 1 indicates the start of contact (protocol A), with 0 means
+ // lifting the contact.
+ touch_finger_down_ = (ev.value == 1);
+ }
+
+ // Intentionally ignore BTN_TOUCH and BTN_TOOL_FINGER, which would otherwise trigger
+ // additional scrolling (because in ScreenRecoveryUI::ShowFile(), we consider keys other than
+ // KEY_POWER and KEY_UP as KEY_DOWN).
+ if (ev.code == BTN_TOUCH || ev.code == BTN_TOOL_FINGER) {
+ return 0;
+ }
+ }
+
ProcessKey(ev.code, ev.value);
}
@@ -365,6 +508,14 @@ bool RecoveryUI::HasThreeButtons() {
return has_power_key && has_up_key && has_down_key;
}
+bool RecoveryUI::HasPowerKey() const {
+ return has_power_key;
+}
+
+bool RecoveryUI::HasTouchScreen() const {
+ return has_touch_screen;
+}
+
void RecoveryUI::FlushKeys() {
pthread_mutex_lock(&key_queue_mutex);
key_queue_len = 0;
@@ -377,8 +528,8 @@ RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) {
pthread_mutex_unlock(&key_queue_mutex);
// If we have power and volume up keys, that chord is the signal to toggle the text display.
- if (HasThreeButtons()) {
- if (key == KEY_VOLUMEUP && IsKeyPressed(KEY_POWER)) {
+ if (HasThreeButtons() || (HasPowerKey() && HasTouchScreen() && touch_screen_allowed_)) {
+ if ((key == KEY_VOLUMEUP || key == KEY_UP) && IsKeyPressed(KEY_POWER)) {
return TOGGLE;
}
} else {
diff --git a/ui.h b/ui.h
index 7eb04aec8..3d9afece0 100644
--- a/ui.h
+++ b/ui.h
@@ -82,6 +82,12 @@ class RecoveryUI {
// otherwise.
virtual bool HasThreeButtons();
+ // Returns true if it has a power key.
+ virtual bool HasPowerKey() const;
+
+ // Returns true if it supports touch inputs.
+ virtual bool HasTouchScreen() const;
+
// Erases any queued-up keys.
virtual void FlushKeys();
@@ -129,7 +135,14 @@ class RecoveryUI {
unsigned int brightness_normal_;
unsigned int brightness_dimmed_;
+ // Whether we should listen for touch inputs (default: false).
+ bool touch_screen_allowed_;
+
private:
+ // The sensitivity when detecting a swipe.
+ const int kTouchLowThreshold;
+ const int kTouchHighThreshold;
+
// Key event input queue
pthread_mutex_t key_queue_mutex;
pthread_cond_t key_queue_cond;
@@ -147,6 +160,17 @@ class RecoveryUI {
bool has_power_key;
bool has_up_key;
bool has_down_key;
+ bool has_touch_screen;
+
+ // Touch event related variables. See the comments in RecoveryUI::OnInputEvent().
+ int touch_slot_;
+ int touch_X_;
+ int touch_Y_;
+ int touch_start_X_;
+ int touch_start_Y_;
+ bool touch_finger_down_;
+ bool touch_swiping_;
+ bool is_bootreason_recovery_ui_;
struct key_timer_t {
RecoveryUI* ui;
@@ -157,6 +181,7 @@ class RecoveryUI {
pthread_t input_thread_;
void OnKeyDetected(int key_code);
+ void OnTouchDetected(int dx, int dy);
int OnInputEvent(int fd, uint32_t epevents);
void ProcessKey(int key_code, int updown);
diff --git a/uncrypt/Android.mk b/uncrypt/Android.mk
index 59084b0bb..cb60c721e 100644
--- a/uncrypt/Android.mk
+++ b/uncrypt/Android.mk
@@ -16,7 +16,6 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_CLANG := true
LOCAL_SRC_FILES := uncrypt.cpp
LOCAL_C_INCLUDES := $(LOCAL_PATH)/..
LOCAL_MODULE := uncrypt
diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp
index 07d183be2..7a2ccbc7c 100644
--- a/uncrypt/uncrypt.cpp
+++ b/uncrypt/uncrypt.cpp
@@ -448,20 +448,20 @@ static int produce_block_map(const char* path, const char* map_file, const char*
static int uncrypt(const char* input_path, const char* map_file, const int socket) {
LOG(INFO) << "update package is \"" << input_path << "\"";
- // Turn the name of the file we're supposed to convert into an
- // absolute path, so we can find what filesystem it's on.
+ // Turn the name of the file we're supposed to convert into an absolute path, so we can find
+ // what filesystem it's on.
char path[PATH_MAX+1];
- if (realpath(input_path, path) == NULL) {
+ if (realpath(input_path, path) == nullptr) {
PLOG(ERROR) << "failed to convert \"" << input_path << "\" to absolute path";
- return 1;
+ return kUncryptRealpathFindError;
}
bool encryptable;
bool encrypted;
const char* blk_dev = find_block_device(path, &encryptable, &encrypted);
- if (blk_dev == NULL) {
+ if (blk_dev == nullptr) {
LOG(ERROR) << "failed to find block device for " << path;
- return 1;
+ return kUncryptBlockDeviceFindError;
}
// If the filesystem it's on isn't encrypted, we only produce the
@@ -625,12 +625,12 @@ int main(int argc, char** argv) {
}
if (action == UNCRYPT_DEBUG) {
- LOG(INFO) << "uncrypt called in debug mode, skip socket communication\n";
+ LOG(INFO) << "uncrypt called in debug mode, skip socket communication";
bool success = uncrypt_wrapper(input_path, map_file, -1);
if (success) {
- LOG(INFO) << "uncrypt succeeded\n";
+ LOG(INFO) << "uncrypt succeeded";
} else{
- LOG(INFO) << "uncrypt failed\n";
+ LOG(INFO) << "uncrypt failed";
}
return success ? 0 : 1;
}
diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp
index d3a5185b8..faebbede0 100644
--- a/update_verifier/update_verifier.cpp
+++ b/update_verifier/update_verifier.cpp
@@ -45,6 +45,7 @@
#include <unistd.h>
#include <algorithm>
+#include <future>
#include <string>
#include <vector>
@@ -123,11 +124,6 @@ static bool read_blocks(const std::string& partition, const std::string& range_s
LOG(ERROR) << "Failed to find dm block device for " << partition;
return false;
}
- android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
- if (fd.get() == -1) {
- PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition;
- return false;
- }
// For block range string, first integer 'count' equals 2 * total number of valid ranges,
// followed by 'count' number comma separated integers. Every two integers reprensent a
@@ -142,73 +138,110 @@ static bool read_blocks(const std::string& partition, const std::string& range_s
return false;
}
- size_t blk_count = 0;
- for (size_t i = 1; i < ranges.size(); i += 2) {
- unsigned int range_start, range_end;
- bool parse_status = android::base::ParseUint(ranges[i], &range_start);
- parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end);
- if (!parse_status || range_start >= range_end) {
- LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i + 1];
- return false;
- }
+ std::vector<std::future<bool>> threads;
+ size_t thread_num = std::thread::hardware_concurrency() ?: 4;
+ thread_num = std::min(thread_num, range_count / 2);
+ size_t group_range_count = range_count / thread_num;
- static constexpr size_t BLOCKSIZE = 4096;
- if (lseek64(fd.get(), static_cast<off64_t>(range_start) * BLOCKSIZE, SEEK_SET) == -1) {
- PLOG(ERROR) << "lseek to " << range_start << " failed";
- return false;
- }
-
- size_t remain = (range_end - range_start) * BLOCKSIZE;
- while (remain > 0) {
- size_t to_read = std::min(remain, 1024 * BLOCKSIZE);
- std::vector<uint8_t> buf(to_read);
- if (!android::base::ReadFully(fd.get(), buf.data(), to_read)) {
- PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end;
+ for (size_t t = 0; t < thread_num; t++) {
+ auto thread_func = [t, group_range_count, &dm_block_device, &ranges, &partition]() {
+ size_t blk_count = 0;
+ static constexpr size_t kBlockSize = 4096;
+ std::vector<uint8_t> buf(1024 * kBlockSize);
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
+ if (fd.get() == -1) {
+ PLOG(ERROR) << "Error reading " << dm_block_device << " for partition " << partition;
return false;
}
- remain -= to_read;
- }
- blk_count += (range_end - range_start);
+
+ for (size_t i = 1 + group_range_count * t; i < group_range_count * (t + 1) + 1; i += 2) {
+ unsigned int range_start, range_end;
+ bool parse_status = android::base::ParseUint(ranges[i], &range_start);
+ parse_status = parse_status && android::base::ParseUint(ranges[i + 1], &range_end);
+ if (!parse_status || range_start >= range_end) {
+ LOG(ERROR) << "Invalid range pair " << ranges[i] << ", " << ranges[i + 1];
+ return false;
+ }
+
+ if (lseek64(fd.get(), static_cast<off64_t>(range_start) * kBlockSize, SEEK_SET) == -1) {
+ PLOG(ERROR) << "lseek to " << range_start << " failed";
+ return false;
+ }
+
+ size_t remain = (range_end - range_start) * kBlockSize;
+ while (remain > 0) {
+ size_t to_read = std::min(remain, 1024 * kBlockSize);
+ if (!android::base::ReadFully(fd.get(), buf.data(), to_read)) {
+ PLOG(ERROR) << "Failed to read blocks " << range_start << " to " << range_end;
+ return false;
+ }
+ remain -= to_read;
+ }
+ blk_count += (range_end - range_start);
+ }
+ LOG(INFO) << "Finished reading " << blk_count << " blocks on " << dm_block_device;
+ return true;
+ };
+
+ threads.emplace_back(std::async(std::launch::async, thread_func));
}
- LOG(INFO) << "Finished reading " << blk_count << " blocks on " << dm_block_device;
- return true;
+ bool ret = true;
+ for (auto& t : threads) {
+ ret = t.get() && ret;
+ }
+ LOG(INFO) << "Finished reading blocks on " << dm_block_device << " with " << thread_num
+ << " threads.";
+ return ret;
}
+// Returns true to indicate a passing verification (or the error should be ignored); Otherwise
+// returns false on fatal errors, where we should reject the current boot and trigger a fallback.
+// Note that update_verifier should be backward compatible to not reject care_map.txt from old
+// releases, which could otherwise fail to boot into the new release. For example, we've changed
+// the care_map format between N and O. An O update_verifier would fail to work with N
+// care_map.txt. This could be a result of sideloading an O OTA while the device having a pending N
+// update.
bool verify_image(const std::string& care_map_name) {
- android::base::unique_fd care_map_fd(TEMP_FAILURE_RETRY(open(care_map_name.c_str(), O_RDONLY)));
- // If the device is flashed before the current boot, it may not have care_map.txt
- // in /data/ota_package. To allow the device to continue booting in this situation,
- // we should print a warning and skip the block verification.
- if (care_map_fd.get() == -1) {
- PLOG(WARNING) << "Failed to open " << care_map_name;
- return true;
- }
- // Care map file has four lines (two lines if vendor partition is not present):
- // First line has the block partition name (system/vendor).
- // Second line holds all ranges of blocks to verify.
- // The next two lines have the same format but for vendor partition.
- std::string file_content;
- if (!android::base::ReadFdToString(care_map_fd.get(), &file_content)) {
- LOG(ERROR) << "Error reading care map contents to string.";
- return false;
- }
+ android::base::unique_fd care_map_fd(TEMP_FAILURE_RETRY(open(care_map_name.c_str(), O_RDONLY)));
+ // If the device is flashed before the current boot, it may not have care_map.txt
+ // in /data/ota_package. To allow the device to continue booting in this situation,
+ // we should print a warning and skip the block verification.
+ if (care_map_fd.get() == -1) {
+ PLOG(WARNING) << "Failed to open " << care_map_name;
+ return true;
+ }
+ // Care map file has four lines (two lines if vendor partition is not present):
+ // First line has the block partition name (system/vendor).
+ // Second line holds all ranges of blocks to verify.
+ // The next two lines have the same format but for vendor partition.
+ std::string file_content;
+ if (!android::base::ReadFdToString(care_map_fd.get(), &file_content)) {
+ LOG(ERROR) << "Error reading care map contents to string.";
+ return false;
+ }
- std::vector<std::string> lines;
- lines = android::base::Split(android::base::Trim(file_content), "\n");
- if (lines.size() != 2 && lines.size() != 4) {
- LOG(ERROR) << "Invalid lines in care_map: found " << lines.size()
- << " lines, expecting 2 or 4 lines.";
- return false;
- }
+ std::vector<std::string> lines;
+ lines = android::base::Split(android::base::Trim(file_content), "\n");
+ if (lines.size() != 2 && lines.size() != 4) {
+ LOG(ERROR) << "Invalid lines in care_map: found " << lines.size()
+ << " lines, expecting 2 or 4 lines.";
+ return false;
+ }
- for (size_t i = 0; i < lines.size(); i += 2) {
- if (!read_blocks(lines[i], lines[i+1])) {
- return false;
- }
+ for (size_t i = 0; i < lines.size(); i += 2) {
+ // We're seeing an N care_map.txt. Skip the verification since it's not compatible with O
+ // update_verifier (the last few metadata blocks can't be read via device mapper).
+ if (android::base::StartsWith(lines[i], "/dev/block/")) {
+ LOG(WARNING) << "Found legacy care_map.txt; skipped.";
+ return true;
+ }
+ if (!read_blocks(lines[i], lines[i+1])) {
+ return false;
}
+ }
- return true;
+ return true;
}
static int reboot_device() {
@@ -239,23 +272,36 @@ int update_verifier(int argc, char** argv) {
// The current slot has not booted successfully.
#if defined(PRODUCT_SUPPORTS_VERITY) || defined(BOARD_AVB_ENABLE)
+ bool skip_verification = false;
std::string verity_mode = android::base::GetProperty("ro.boot.veritymode", "");
if (verity_mode.empty()) {
+ // With AVB it's possible to disable verification entirely and
+ // in this case ro.boot.veritymode is empty.
+#if defined(BOARD_AVB_ENABLE)
+ LOG(WARNING) << "verification has been disabled; marking without verification.";
+ skip_verification = true;
+#else
LOG(ERROR) << "Failed to get dm-verity mode.";
return reboot_device();
+#endif
} else if (android::base::EqualsIgnoreCase(verity_mode, "eio")) {
// We shouldn't see verity in EIO mode if the current slot hasn't booted successfully before.
// Continue the verification until we fail to read some blocks.
LOG(WARNING) << "Found dm-verity in EIO mode.";
+ } else if (android::base::EqualsIgnoreCase(verity_mode, "disabled")) {
+ LOG(WARNING) << "dm-verity in disabled mode; marking without verification.";
+ skip_verification = true;
} else if (verity_mode != "enforcing") {
LOG(ERROR) << "Unexpected dm-verity mode : " << verity_mode << ", expecting enforcing.";
return reboot_device();
}
- static constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt";
- if (!verify_image(CARE_MAP_FILE)) {
- LOG(ERROR) << "Failed to verify all blocks in care map file.";
- return reboot_device();
+ if (!skip_verification) {
+ static constexpr auto CARE_MAP_FILE = "/data/ota_package/care_map.txt";
+ if (!verify_image(CARE_MAP_FILE)) {
+ LOG(ERROR) << "Failed to verify all blocks in care map file.";
+ return reboot_device();
+ }
}
#else
LOG(WARNING) << "dm-verity not enabled; marking without verification.";
diff --git a/update_verifier/update_verifier_main.cpp b/update_verifier/update_verifier_main.cpp
index 46e8bbb59..9dd5a0cc4 100644
--- a/update_verifier/update_verifier_main.cpp
+++ b/update_verifier/update_verifier_main.cpp
@@ -16,8 +16,14 @@
// See the comments in update_verifier.cpp.
+#include <android-base/logging.h>
+
#include "update_verifier/update_verifier.h"
int main(int argc, char** argv) {
+ // Set up update_verifier logging to be written to kmsg; because we may not have Logd during
+ // boot time.
+ android::base::InitLogging(argv, &android::base::KernelLogger);
+
return update_verifier(argc, argv);
}
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 2bec487fe..fe21dd0eb 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -53,8 +53,8 @@
#include "error_code.h"
#include "ota_io.h"
#include "print_sha1.h"
+#include "rangeset.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
@@ -158,20 +158,22 @@ class RangeSinkWriter {
CHECK_NE(tgt.size(), static_cast<size_t>(0));
};
- virtual ~RangeSinkWriter() {};
-
bool Finished() const {
return next_range_ == tgt_.size() && current_range_left_ == 0;
}
- // Return number of bytes consumed; and 0 indicates a writing failure.
- virtual size_t Write(const uint8_t* data, size_t size) {
+ size_t AvailableSpace() const {
+ return tgt_.blocks() * BLOCKSIZE - bytes_written_;
+ }
+
+ // Return number of bytes written; and 0 indicates a writing failure.
+ 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 consumed = 0;
+ size_t written = 0;
while (size > 0) {
// Move to the next range as needed.
if (!SeekToOutputRange()) {
@@ -191,18 +193,18 @@ class RangeSinkWriter {
size -= write_now;
current_range_left_ -= write_now;
- consumed += write_now;
+ written += write_now;
}
- bytes_written_ += consumed;
- return consumed;
+ bytes_written_ += written;
+ return written;
}
size_t BytesWritten() const {
return bytes_written_;
}
- protected:
+ private:
// Set up the output cursor, move to next range if needed.
bool SeekToOutputRange() {
// We haven't finished the current range yet.
@@ -241,75 +243,6 @@ class RangeSinkWriter {
size_t bytes_written_;
};
-class BrotliNewDataWriter : public RangeSinkWriter {
- public:
- BrotliNewDataWriter(int fd, const RangeSet& tgt, BrotliDecoderState* state)
- : RangeSinkWriter(fd, tgt), state_(state) {}
-
- size_t Write(const uint8_t* data, size_t size) override {
- if (Finished()) {
- LOG(ERROR) << "Brotli new data write overrun; can't write " << size << " bytes";
- return 0;
- }
- CHECK(state_ != nullptr);
-
- size_t consumed = 0;
- while (true) {
- // Move to the next range as needed.
- if (!SeekToOutputRange()) {
- break;
- }
-
- size_t available_in = size;
- size_t write_now = std::min<size_t>(32768, current_range_left_);
- uint8_t buffer[write_now];
-
- size_t available_out = write_now;
- uint8_t* next_out = buffer;
-
- // The brotli decoder will update |data|, |available_in|, |next_out| and |available_out|.
- BrotliDecoderResult result = BrotliDecoderDecompressStream(
- state_, &available_in, &data, &available_out, &next_out, nullptr);
-
- // We don't have a way to recover from the decode error; report the failure.
- if (result == BROTLI_DECODER_RESULT_ERROR) {
- LOG(ERROR) << "Decompression failed with "
- << BrotliDecoderErrorString(BrotliDecoderGetErrorCode(state_));
- return 0;
- }
-
- if (write_all(fd_, buffer, write_now - available_out) == -1) {
- return 0;
- }
-
- LOG(DEBUG) << "bytes written: " << write_now - available_out << ", bytes consumed "
- << size - available_in << ", decoder status " << result;
-
- // Update the total bytes written to output by the current writer; this is different from the
- // consumed input bytes.
- bytes_written_ += write_now - available_out;
- current_range_left_ -= (write_now - available_out);
- consumed += (size - available_in);
-
- // Update the remaining size. The input data ptr is already updated by brotli decoder
- // function.
- size = available_in;
-
- // Continue if we have more output to write, or more input to consume.
- if (result == BROTLI_DECODER_RESULT_SUCCESS ||
- (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT && size == 0)) {
- break;
- }
- }
-
- return consumed;
- }
-
- private:
- // Pointer to the decoder state. (initialized by PerformBlockImageUpdate)
- BrotliDecoderState* state_;
-};
-
/**
* 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
@@ -354,16 +287,73 @@ static bool receive_new_data(const uint8_t* data, size_t size, void* cookie) {
// At this point nti->writer is set, and we own it. The main thread is waiting for it to
// disappear from nti.
- size_t consumed = nti->writer->Write(data, size);
+ size_t write_now = std::min(size, nti->writer->AvailableSpace());
+ if (nti->writer->Write(data, write_now) != write_now) {
+ LOG(ERROR) << "Failed to write " << write_now << " bytes.";
+ return false;
+ }
+
+ data += write_now;
+ size -= write_now;
+
+ 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 bool receive_brotli_new_data(const uint8_t* data, size_t size, void* cookie) {
+ NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie);
+
+ while (size > 0 || BrotliDecoderHasMoreOutput(nti->brotli_decoder_state)) {
+ // Wait for nti->writer to be non-null, indicating some of this data is wanted.
+ pthread_mutex_lock(&nti->mu);
+ while (nti->writer == nullptr) {
+ pthread_cond_wait(&nti->cv, &nti->mu);
+ }
+ pthread_mutex_unlock(&nti->mu);
+
+ // At this point nti->writer is set, and we own it. The main thread is waiting for it to
+ // disappear from nti.
+
+ size_t buffer_size = std::min<size_t>(32768, nti->writer->AvailableSpace());
+ if (buffer_size == 0) {
+ LOG(ERROR) << "No space left in output range";
+ return false;
+ }
+ uint8_t buffer[buffer_size];
+ size_t available_in = size;
+ size_t available_out = buffer_size;
+ uint8_t* next_out = buffer;
+
+ // The brotli decoder will update |data|, |available_in|, |next_out| and |available_out|.
+ BrotliDecoderResult result = BrotliDecoderDecompressStream(
+ nti->brotli_decoder_state, &available_in, &data, &available_out, &next_out, nullptr);
- // We encounter a fatal error if we fail to consume any input bytes. If this happens, abort the
- // extraction.
- if (consumed == 0) {
- LOG(ERROR) << "Failed to process " << size << " input bytes.";
+ if (result == BROTLI_DECODER_RESULT_ERROR) {
+ LOG(ERROR) << "Decompression failed with "
+ << BrotliDecoderErrorString(BrotliDecoderGetErrorCode(nti->brotli_decoder_state));
return false;
}
- data += consumed;
- size -= consumed;
+
+ LOG(DEBUG) << "bytes to write: " << buffer_size - available_out << ", bytes consumed "
+ << size - available_in << ", decoder status " << result;
+
+ size_t write_now = buffer_size - available_out;
+ if (nti->writer->Write(buffer, write_now) != write_now) {
+ LOG(ERROR) << "Failed to write " << write_now << " bytes.";
+ return false;
+ }
+
+ // Update the remaining size. The input data ptr is already updated by brotli decoder function.
+ size = available_in;
if (nti->writer->Finished()) {
// We have written all the bytes desired by this writer.
@@ -380,8 +370,11 @@ static bool receive_new_data(const uint8_t* data, size_t size, void* cookie) {
static void* unzip_new_data(void* cookie) {
NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie);
- ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti);
-
+ if (nti->brotli_compressed) {
+ ProcessZipEntryContents(nti->za, &nti->entry, receive_brotli_new_data, nti);
+ } else {
+ ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti);
+ }
pthread_mutex_lock(&nti->mu);
nti->receiver_available = false;
if (nti->writer != nullptr) {
@@ -1240,12 +1233,7 @@ static int PerformCommandNew(CommandParameters& params) {
LOG(INFO) << " writing " << tgt.blocks() << " blocks of new data";
pthread_mutex_lock(&params.nti.mu);
- if (params.nti.brotli_compressed) {
- params.nti.writer =
- std::make_unique<BrotliNewDataWriter>(params.fd, tgt, params.nti.brotli_decoder_state);
- } else {
- params.nti.writer = std::make_unique<RangeSinkWriter>(params.fd, tgt);
- }
+ params.nti.writer = std::make_unique<RangeSinkWriter>(params.fd, tgt);
pthread_cond_broadcast(&params.nti.cv);
while (params.nti.writer != nullptr) {
@@ -1485,7 +1473,6 @@ static Value* PerformBlockImageUpdate(const char* name, State* state,
if (params.canwrite) {
params.nti.za = za;
params.nti.entry = new_entry;
- // The entry is compressed by brotli if has a 'br' extension.
params.nti.brotli_compressed = android::base::EndsWith(new_data_fn->data, ".br");
if (params.nti.brotli_compressed) {
// Initialize brotli decoder state.
diff --git a/updater/install.cpp b/updater/install.cpp
index ff79edce0..8e54c2e75 100644
--- a/updater/install.cpp
+++ b/updater/install.cpp
@@ -95,34 +95,6 @@ void uiPrintf(State* _Nonnull state, const char* _Nonnull format, ...) {
uiPrint(state, error_msg);
}
-static bool is_dir(const std::string& dirpath) {
- struct stat st;
- return stat(dirpath.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
-}
-
-// Create all parent directories of name, if necessary.
-static bool make_parents(const std::string& name) {
- size_t prev_end = 0;
- while (prev_end < name.size()) {
- size_t next_end = name.find('/', prev_end + 1);
- if (next_end == std::string::npos) {
- break;
- }
- std::string dir_path = name.substr(0, next_end);
- if (!is_dir(dir_path)) {
- int result = mkdir(dir_path.c_str(), 0700);
- if (result != 0) {
- PLOG(ERROR) << "failed to mkdir " << dir_path << " when make parents for " << name;
- return false;
- }
-
- LOG(INFO) << "created [" << dir_path << "]";
- }
- prev_end = next_end;
- }
- return true;
-}
-
// mount(fs_type, partition_type, location, mount_point)
// mount(fs_type, partition_type, location, mount_point, mount_options)
@@ -302,9 +274,31 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt
}
if (fs_type == "ext4") {
- int status = make_ext4fs(location.c_str(), size, mount_point.c_str(), sehandle);
+ const char* mke2fs_argv[] = { "/sbin/mke2fs_static", "-t", "ext4", "-b", "4096",
+ location.c_str(), nullptr, nullptr };
+ std::string size_str;
+ if (size != 0) {
+ size_str = std::to_string(size / 4096LL);
+ mke2fs_argv[6] = size_str.c_str();
+ }
+
+ int status = exec_cmd(mke2fs_argv[0], const_cast<char**>(mke2fs_argv));
+ if (status != 0) {
+ LOG(WARNING) << name << ": mke2fs failed (" << status << ") on " << location
+ << ", falling back to make_ext4fs";
+ status = make_ext4fs(location.c_str(), size, mount_point.c_str(), sehandle);
+ if (status != 0) {
+ LOG(ERROR) << name << ": make_ext4fs failed (" << status << ") on " << location;
+ return StringValue("");
+ }
+ return StringValue(location);
+ }
+
+ const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static", "-e", "-a", mount_point.c_str(),
+ location.c_str(), nullptr };
+ status = exec_cmd(e2fsdroid_argv[0], const_cast<char**>(e2fsdroid_argv));
if (status != 0) {
- LOG(ERROR) << name << ": make_ext4fs failed (" << status << ") on " << location;
+ LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location;
return StringValue("");
}
return StringValue(location);
diff --git a/updater/updater.cpp b/updater/updater.cpp
index f5ff6df91..1d8fa8e92 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -25,6 +25,7 @@
#include <android-base/logging.h>
#include <android-base/strings.h>
+#include <selinux/android.h>
#include <selinux/label.h>
#include <selinux/selinux.h>
#include <ziparchive/zip_archive.h>
@@ -139,9 +140,8 @@ int main(int argc, char** argv) {
return 6;
}
- struct selinux_opt seopts[] = { { SELABEL_OPT_PATH, "/file_contexts" } };
-
- sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1);
+ sehandle = selinux_android_file_context_handle();
+ selinux_android_set_sehandle(sehandle);
if (!sehandle) {
fprintf(cmd_pipe, "ui_print Warning: No file_contexts\n");
diff --git a/verifier.cpp b/verifier.cpp
index 2ef9c4c37..18437fb7a 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -474,81 +474,80 @@ std::unique_ptr<EC_KEY, ECKEYDeleter> parse_ec_key(FILE* file) {
// Otherwise returns false if the file failed to parse, or if it contains zero
// keys. The contents in certs would be unspecified on failure.
bool load_keys(const char* filename, std::vector<Certificate>& certs) {
- std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "r"), fclose);
- if (!f) {
- PLOG(ERROR) << "error opening " << filename;
- return false;
- }
-
- while (true) {
- certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
- Certificate& cert = certs.back();
- uint32_t exponent = 0;
-
- char start_char;
- if (fscanf(f.get(), " %c", &start_char) != 1) return false;
- if (start_char == '{') {
- // a version 1 key has no version specifier.
- cert.key_type = Certificate::KEY_TYPE_RSA;
- exponent = 3;
- cert.hash_len = SHA_DIGEST_LENGTH;
- } else if (start_char == 'v') {
- int version;
- if (fscanf(f.get(), "%d {", &version) != 1) return false;
- switch (version) {
- case 2:
- cert.key_type = Certificate::KEY_TYPE_RSA;
- exponent = 65537;
- cert.hash_len = SHA_DIGEST_LENGTH;
- break;
- case 3:
- cert.key_type = Certificate::KEY_TYPE_RSA;
- exponent = 3;
- cert.hash_len = SHA256_DIGEST_LENGTH;
- break;
- case 4:
- cert.key_type = Certificate::KEY_TYPE_RSA;
- exponent = 65537;
- cert.hash_len = SHA256_DIGEST_LENGTH;
- break;
- case 5:
- cert.key_type = Certificate::KEY_TYPE_EC;
- cert.hash_len = SHA256_DIGEST_LENGTH;
- break;
- default:
- return false;
- }
- }
+ std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "re"), fclose);
+ if (!f) {
+ PLOG(ERROR) << "error opening " << filename;
+ return false;
+ }
- if (cert.key_type == Certificate::KEY_TYPE_RSA) {
- cert.rsa = parse_rsa_key(f.get(), exponent);
- if (!cert.rsa) {
- return false;
- }
+ while (true) {
+ certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ Certificate& cert = certs.back();
+ uint32_t exponent = 0;
+
+ char start_char;
+ if (fscanf(f.get(), " %c", &start_char) != 1) return false;
+ if (start_char == '{') {
+ // a version 1 key has no version specifier.
+ cert.key_type = Certificate::KEY_TYPE_RSA;
+ exponent = 3;
+ cert.hash_len = SHA_DIGEST_LENGTH;
+ } else if (start_char == 'v') {
+ int version;
+ if (fscanf(f.get(), "%d {", &version) != 1) return false;
+ switch (version) {
+ case 2:
+ cert.key_type = Certificate::KEY_TYPE_RSA;
+ exponent = 65537;
+ cert.hash_len = SHA_DIGEST_LENGTH;
+ break;
+ case 3:
+ cert.key_type = Certificate::KEY_TYPE_RSA;
+ exponent = 3;
+ cert.hash_len = SHA256_DIGEST_LENGTH;
+ break;
+ case 4:
+ cert.key_type = Certificate::KEY_TYPE_RSA;
+ exponent = 65537;
+ cert.hash_len = SHA256_DIGEST_LENGTH;
+ break;
+ case 5:
+ cert.key_type = Certificate::KEY_TYPE_EC;
+ cert.hash_len = SHA256_DIGEST_LENGTH;
+ break;
+ default:
+ return false;
+ }
+ }
- LOG(INFO) << "read key e=" << exponent << " hash=" << cert.hash_len;
- } else if (cert.key_type == Certificate::KEY_TYPE_EC) {
- cert.ec = parse_ec_key(f.get());
- if (!cert.ec) {
- return false;
- }
- } else {
- LOG(ERROR) << "Unknown key type " << cert.key_type;
- return false;
- }
+ if (cert.key_type == Certificate::KEY_TYPE_RSA) {
+ cert.rsa = parse_rsa_key(f.get(), exponent);
+ if (!cert.rsa) {
+ return false;
+ }
- // if the line ends in a comma, this file has more keys.
- int ch = fgetc(f.get());
- if (ch == ',') {
- // more keys to come.
- continue;
- } else if (ch == EOF) {
- break;
- } else {
- LOG(ERROR) << "unexpected character between keys";
- return false;
- }
+ LOG(INFO) << "read key e=" << exponent << " hash=" << cert.hash_len;
+ } else if (cert.key_type == Certificate::KEY_TYPE_EC) {
+ cert.ec = parse_ec_key(f.get());
+ if (!cert.ec) {
+ return false;
+ }
+ } else {
+ LOG(ERROR) << "Unknown key type " << cert.key_type;
+ return false;
}
- return true;
+ // if the line ends in a comma, this file has more keys.
+ int ch = fgetc(f.get());
+ if (ch == ',') {
+ // more keys to come.
+ continue;
+ } else if (ch == EOF) {
+ break;
+ } else {
+ LOG(ERROR) << "unexpected character between keys";
+ return false;
+ }
+ }
+ return true;
}
diff --git a/wear_device.cpp b/wear_device.cpp
new file mode 100644
index 000000000..3268130b0
--- /dev/null
+++ b/wear_device.cpp
@@ -0,0 +1,23 @@
+/*
+ * 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 "device.h"
+#include "wear_ui.h"
+
+Device* make_device() {
+ return new Device(new WearRecoveryUI);
+}
+
diff --git a/wear_touch.cpp b/wear_touch.cpp
deleted file mode 100644
index e2ab44d2d..000000000
--- a/wear_touch.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2016 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 <dirent.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
-
-#include <android-base/logging.h>
-#include <linux/input.h>
-
-#include "wear_touch.h"
-
-#define DEVICE_PATH "/dev/input"
-
-WearSwipeDetector::WearSwipeDetector(int low, int high, OnSwipeCallback callback, void* cookie):
- mLowThreshold(low),
- mHighThreshold(high),
- mCallback(callback),
- mCookie(cookie),
- mCurrentSlot(-1) {
- pthread_create(&mThread, NULL, touch_thread, this);
-}
-
-WearSwipeDetector::~WearSwipeDetector() {
-}
-
-void WearSwipeDetector::detect(int dx, int dy) {
- enum SwipeDirection direction;
-
- if (abs(dy) < mLowThreshold && abs(dx) > mHighThreshold) {
- direction = dx < 0 ? LEFT : RIGHT;
- } else if (abs(dx) < mLowThreshold && abs(dy) > mHighThreshold) {
- direction = dy < 0 ? UP : DOWN;
- } else {
- LOG(DEBUG) << "Ignore " << dx << " " << dy;
- return;
- }
-
- LOG(DEBUG) << "Swipe direction=" << direction;
- mCallback(mCookie, direction);
-}
-
-void WearSwipeDetector::process(struct input_event *event) {
- if (mCurrentSlot < 0) {
- mCallback(mCookie, UP);
- mCurrentSlot = 0;
- }
-
- if (event->type == EV_ABS) {
- if (event->code == ABS_MT_SLOT)
- mCurrentSlot = event->value;
-
- // Ignore other fingers
- if (mCurrentSlot > 0) {
- return;
- }
-
- switch (event->code) {
- case ABS_MT_POSITION_X:
- mX = event->value;
- mFingerDown = true;
- break;
-
- case ABS_MT_POSITION_Y:
- mY = event->value;
- mFingerDown = true;
- break;
-
- case ABS_MT_TRACKING_ID:
- if (event->value < 0)
- mFingerDown = false;
- break;
- }
- } else if (event->type == EV_SYN) {
- if (event->code == SYN_REPORT) {
- if (mFingerDown && !mSwiping) {
- mStartX = mX;
- mStartY = mY;
- mSwiping = true;
- } else if (!mFingerDown && mSwiping) {
- mSwiping = false;
- detect(mX - mStartX, mY - mStartY);
- }
- }
- }
-}
-
-void WearSwipeDetector::run() {
- int fd = findDevice(DEVICE_PATH);
- if (fd < 0) {
- LOG(ERROR) << "no input devices found";
- return;
- }
-
- struct input_event event;
- while (read(fd, &event, sizeof(event)) == sizeof(event)) {
- process(&event);
- }
-
- close(fd);
-}
-
-void* WearSwipeDetector::touch_thread(void* cookie) {
- (static_cast<WearSwipeDetector*>(cookie))->run();
- return NULL;
-}
-
-#define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)%8)))
-
-int WearSwipeDetector::openDevice(const char *device) {
- int fd = open(device, O_RDONLY);
- if (fd < 0) {
- PLOG(ERROR) << "could not open " << device;
- return false;
- }
-
- char name[80];
- name[sizeof(name) - 1] = '\0';
- if (ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {
- PLOG(ERROR) << "could not get device name for " << device;
- name[0] = '\0';
- }
-
- uint8_t bits[512];
- memset(bits, 0, sizeof(bits));
- int ret = ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(bits)), bits);
- if (ret > 0) {
- if (test_bit(ABS_MT_POSITION_X, bits) && test_bit(ABS_MT_POSITION_Y, bits)) {
- LOG(DEBUG) << "Found " << device << " " << name;
- return fd;
- }
- }
-
- close(fd);
- return -1;
-}
-
-int WearSwipeDetector::findDevice(const char* path) {
- DIR* dir = opendir(path);
- if (dir == NULL) {
- PLOG(ERROR) << "Could not open directory " << path;
- return false;
- }
-
- struct dirent* entry;
- int ret = -1;
- while (ret < 0 && (entry = readdir(dir)) != NULL) {
- if (entry->d_name[0] == '.') continue;
-
- char device[PATH_MAX];
- device[PATH_MAX-1] = '\0';
- snprintf(device, PATH_MAX-1, "%s/%s", path, entry->d_name);
-
- ret = openDevice(device);
- }
-
- closedir(dir);
- return ret;
-}
-
diff --git a/wear_touch.h b/wear_touch.h
deleted file mode 100644
index 9a1d3150c..000000000
--- a/wear_touch.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __WEAR_TOUCH_H
-#define __WEAR_TOUCH_H
-
-#include <pthread.h>
-
-class WearSwipeDetector {
-
-public:
- enum SwipeDirection { UP, DOWN, RIGHT, LEFT };
- typedef void (*OnSwipeCallback)(void* cookie, enum SwipeDirection direction);
-
- WearSwipeDetector(int low, int high, OnSwipeCallback cb, void* cookie);
- ~WearSwipeDetector();
-
-private:
- void run();
- void process(struct input_event *event);
- void detect(int dx, int dy);
-
- pthread_t mThread;
- static void* touch_thread(void* cookie);
-
- int findDevice(const char* path);
- int openDevice(const char* device);
-
- int mLowThreshold;
- int mHighThreshold;
-
- OnSwipeCallback mCallback;
- void *mCookie;
-
- int mX;
- int mY;
- int mStartX;
- int mStartY;
-
- int mCurrentSlot;
- bool mFingerDown;
- bool mSwiping;
-};
-
-#endif // __WEAR_TOUCH_H
diff --git a/wear_ui.cpp b/wear_ui.cpp
index 18c30d34a..e4806718d 100644
--- a/wear_ui.cpp
+++ b/wear_ui.cpp
@@ -18,6 +18,7 @@
#include <errno.h>
#include <fcntl.h>
+#include <pthread.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
@@ -38,11 +39,6 @@
#include "common.h"
#include "device.h"
-// There's only (at most) one of these objects, and global callbacks
-// (for pthread_create, and the input event system) need to find it,
-// so use a global variable.
-static WearRecoveryUI* self = NULL;
-
// Return the current time as a double (including fractions of a second).
static double now() {
struct timeval tv;
@@ -51,18 +47,22 @@ static double now() {
}
WearRecoveryUI::WearRecoveryUI()
- : progress_bar_y(259), outer_height(0), outer_width(0), menu_unusable_rows(0) {
+ : kProgressBarBaseline(RECOVERY_UI_PROGRESS_BAR_BASELINE),
+ kMenuUnusableRows(RECOVERY_UI_MENU_UNUSABLE_ROWS) {
+ // TODO: kMenuUnusableRows should be computed based on the lines in draw_screen_locked().
+
+ // TODO: The following three variables are likely not needed. The first two are detected
+ // automatically in ScreenRecoveryUI::LoadAnimation(), based on the actual files seen on device.
intro_frames = 22;
loop_frames = 60;
- animation_fps = 30;
- for (size_t i = 0; i < 5; i++) backgroundIcon[i] = NULL;
+ touch_screen_allowed_ = true;
- self = this;
+ for (size_t i = 0; i < 5; i++) backgroundIcon[i] = NULL;
}
int WearRecoveryUI::GetProgressBaseline() const {
- return progress_bar_y;
+ return kProgressBarBaseline;
}
// Draw background frame on the screen. Does not flip pages.
@@ -113,8 +113,8 @@ void WearRecoveryUI::draw_screen_locked() {
SetColor(TEXT_FILL);
gr_fill(0, 0, gr_fb_width(), gr_fb_height());
- int y = outer_height;
- int x = outer_width;
+ int y = kMarginHeight;
+ int x = kMarginWidth;
if (show_menu) {
std::string recovery_fingerprint =
android::base::GetProperty("ro.bootimage.build.fingerprint", "");
@@ -170,7 +170,7 @@ void WearRecoveryUI::draw_screen_locked() {
int ty;
int row = (text_top_ + text_rows_ - 1) % text_rows_;
size_t count = 0;
- for (int ty = gr_fb_height() - char_height_ - outer_height; ty > y + 2 && count < text_rows_;
+ for (int ty = gr_fb_height() - char_height_ - kMarginHeight; ty > y + 2 && count < text_rows_;
ty -= char_height_, ++count) {
gr_text(gr_sys_font(), x + 4, ty, text_[row], 0);
--row;
@@ -185,20 +185,6 @@ void WearRecoveryUI::update_progress_locked() {
gr_flip();
}
-bool WearRecoveryUI::InitTextParams() {
- if (!ScreenRecoveryUI::InitTextParams()) {
- return false;
- }
-
- text_cols_ = (gr_fb_width() - (outer_width * 2)) / char_width_;
-
- if (text_rows_ > kMaxRows) text_rows_ = kMaxRows;
- if (text_cols_ > kMaxCols) text_cols_ = kMaxCols;
-
- visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_;
- return true;
-}
-
bool WearRecoveryUI::Init(const std::string& locale) {
if (!ScreenRecoveryUI::Init(locale)) {
return false;
@@ -263,7 +249,7 @@ void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* it
show_menu = true;
menu_sel = initial_selection;
menu_start = 0;
- menu_end = visible_text_rows - 1 - menu_unusable_rows;
+ menu_end = text_rows_ - 1 - kMenuUnusableRows;
if (menu_items <= menu_end) menu_end = menu_items;
update_screen_locked();
}
diff --git a/wear_ui.h b/wear_ui.h
index a814118c7..9731f4161 100644
--- a/wear_ui.h
+++ b/wear_ui.h
@@ -42,19 +42,14 @@ class WearRecoveryUI : public ScreenRecoveryUI {
protected:
// progress bar vertical position, it's centered horizontally
- int progress_bar_y;
-
- // outer of window
- int outer_height, outer_width;
+ const int kProgressBarBaseline;
// Unusable rows when displaying the recovery menu, including the lines for headers (Android
// Recovery, build id and etc) and the bottom lines that may otherwise go out of the screen.
- int menu_unusable_rows;
+ const int kMenuUnusableRows;
int GetProgressBaseline() const override;
- bool InitTextParams() override;
-
void update_progress_locked() override;
void PrintV(const char*, bool, va_list) override;
@@ -62,17 +57,8 @@ class WearRecoveryUI : public ScreenRecoveryUI {
private:
GRSurface* backgroundIcon[5];
- static const int kMaxCols = 96;
- static const int kMaxRows = 96;
-
- // Number of text rows seen on screen
- int visible_text_rows;
-
- const char* const* menu_headers_;
int menu_start, menu_end;
- pthread_t progress_t;
-
void draw_background_locked() override;
void draw_screen_locked() override;