diff options
40 files changed, 2937 insertions, 2449 deletions
diff --git a/Android.mk b/Android.mk index 95c8823e3..967b9dfbe 100644 --- a/Android.mk +++ b/Android.mk @@ -59,7 +59,8 @@ LOCAL_STATIC_LIBRARIES := \ libvintf_recovery \ libcrypto_utils \ libcrypto \ - libbase + libbase \ + libziparchive \ include $(BUILD_STATIC_LIBRARY) @@ -107,6 +108,18 @@ 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_VR_STEREO_OFFSET),) LOCAL_CFLAGS += -DRECOVERY_UI_VR_STEREO_OFFSET=$(TARGET_RECOVERY_UI_VR_STEREO_OFFSET) else @@ -0,0 +1,3 @@ +enh+aosp-gerrit@google.com +tbao@google.com +xunchang@google.com diff --git a/applypatch/Android.mk b/applypatch/Android.mk index a7412d238..7aed0a95a 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 \ diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp index 41d73ab98..880265260 100644 --- a/applypatch/imgdiff.cpp +++ b/applypatch/imgdiff.cpp @@ -196,7 +196,8 @@ class ImageChunk { size_t DataLengthForPatch() const; void Dump() const { - printf("type %d start %zu len %zu\n", type_, start_, DataLengthForPatch()); + printf("type: %d, start: %zu, len: %zu, name: %s\n", type_, start_, DataLengthForPatch(), + entry_name_.c_str()); } void SetSourceInfo(const ImageChunk& other); @@ -211,7 +212,7 @@ class ImageChunk { 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); + size_t WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset) const; /* * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob @@ -233,6 +234,14 @@ class ImageChunk { bool IsAdjacentNormal(const ImageChunk& other) const; void MergeAdjacentNormal(const ImageChunk& other); + /* + * Compute a bsdiff patch between |this| and the input source chunks. + * 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. + */ + bool MakePatch(const ImageChunk& src, std::vector<uint8_t>* patch_data, saidx_t** bsdiff_cache); + private: int type_; // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW size_t start_; // offset of chunk in the original input file @@ -322,7 +331,7 @@ bool ImageChunk::ChangeChunkToRaw(size_t patch_size) { void ImageChunk::ChangeDeflateChunkToNormal() { if (type_ != CHUNK_DEFLATE) return; type_ = CHUNK_NORMAL; - entry_name_.clear(); + // No need to clear the entry name. uncompressed_data_.clear(); } @@ -345,7 +354,7 @@ size_t ImageChunk::GetHeaderSize(size_t patch_size) const { } } -size_t ImageChunk::WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset) { +size_t ImageChunk::WriteHeaderToFd(int fd, const std::vector<uint8_t>& patch, size_t offset) const { Write4(fd, type_); switch (type_) { case CHUNK_NORMAL: @@ -393,6 +402,68 @@ void ImageChunk::MergeAdjacentNormal(const ImageChunk& other) { raw_data_len_ = raw_data_len_ + other.raw_data_len_; } +bool ImageChunk::MakePatch(const ImageChunk& src, std::vector<uint8_t>* patch_data, + saidx_t** bsdiff_cache) { + if (ChangeChunkToRaw(0)) { + size_t patch_size = DataLengthForPatch(); + patch_data->resize(patch_size); + std::copy(DataForPatch(), 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)); + return false; + } + close(fd); + + int r = bsdiff::bsdiff(src.DataForPatch(), src.DataLengthForPatch(), DataForPatch(), + 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); + // Change the chunk type to raw if the patch takes less space that way. + if (ChangeChunkToRaw(sz)) { + unlink(ptemp); + size_t patch_size = DataLengthForPatch(); + patch_data->resize(patch_size); + std::copy(DataForPatch(), DataForPatch() + patch_size, patch_data->begin()); + return true; + } + 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); + SetSourceInfo(src); + + return true; +} + bool ImageChunk::ReconstructDeflateChunk() { if (type_ != CHUNK_DEFLATE) { printf("attempt to reconstruct non-deflate chunk\n"); @@ -458,195 +529,467 @@ 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"); - return false; +// 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(); + + // In zip mode, 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); + + // Write the contents of |patch_data| to |patch_fd|. + bool WritePatchDataToFd(const std::vector<std::vector<uint8_t>>& patch_data, int patch_fd) const; + + void DumpChunks() const; + + // Non const iterators to access the stored ImageChunks. + std::vector<ImageChunk>::iterator begin() { + return chunks_.begin(); } - // 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); + std::vector<ImageChunk>::iterator end() { + return chunks_.end(); + } + // Return a pointer to the ith ImageChunk. + ImageChunk* Get(size_t i) { + CHECK_LT(i, chunks_.size()); + return &chunks_[i]; + } - 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; + 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. +}; + +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()); + } +} + +ImageChunk* Image::FindChunkByName(const std::string& name, bool find_normal) { + if (name.empty()) { + return nullptr; + } + for (auto& chunk : chunks_) { + if ((chunk.GetType() == CHUNK_DEFLATE || find_normal) && chunk.GetEntryName() == name) { + return &chunk; + } + } + return nullptr; +} + +bool Image::WritePatchDataToFd(const std::vector<std::vector<uint8_t>>& patch_data, + int patch_fd) const { + // 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. + CHECK_EQ(chunks_.size(), patch_data.size()); + size_t total_header_size = 12; + for (size_t i = 0; i < chunks_.size(); ++i) { + total_header_size += chunks_[i].GetHeaderSize(patch_data[i].size()); + } + + 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; + } + Write4(patch_fd, static_cast<int32_t>(chunks_.size())); + for (size_t i = 0; i < chunks_.size(); ++i) { + printf("chunk %zu: ", i); + offset = chunks_[i].WriteHeaderToFd(patch_fd, patch_data[i], offset); + } + + // Append each chunk's bsdiff patch, in order. + for (size_t i = 0; i < chunks_.size(); ++i) { + if (chunks_[i].GetType() != CHUNK_RAW) { + if (!android::base::WriteFully(patch_fd, patch_data[i].data(), patch_data[i].size())) { + printf("failed to write %zu bytes patch for chunk %zu\n", patch_data[i].size(), i); + return false; } } } - // EOCD not found, this file is likely not a valid zip file. - return false; + return true; +} + +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(); + } } -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); +bool Image::ReadFile(const std::string& filename, std::vector<uint8_t>* file_content) { + CHECK(file_content != nullptr); - android::base::unique_fd fd(open(filename, O_RDONLY)); + 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; +} + +class ZipModeImage : public Image { + public: + explicit ZipModeImage(bool is_source) : Image(is_source) {} + + bool Initialize(const std::string& filename) override; + + const ImageChunk& PseudoSource() const { + CHECK(is_source_); + CHECK(pseudo_source_ != nullptr); + return *pseudo_source_; + } + + // 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 patches against the input image, and write the data into |patch_name|. + static bool GeneratePatches(ZipModeImage* tgt_image, 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); + + // The pesudo source chunk for bsdiff if there's no match for the given target chunk. It's in + // fact the whole source file. + std::unique_ptr<ImageChunk> pseudo_source_; +}; + +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 (is_source_) { + pseudo_source_ = std::make_unique<ImageChunk>(CHUNK_NORMAL, 0, &file_content_, zipfile_size); + } + 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); + curr.SetEntryName(entry_name); + curr.SetUncompressedData(std::move(uncompressed_data)); + chunks_.push_back(curr); + } else { + ImageChunk curr(CHUNK_NORMAL, entry->offset, &file_content_, compressed_len); + curr.SetEntryName(entry_name); + chunks_.push_back(curr); + } - android::base::unique_fd fd(open(filename, O_RDONLY)); - if (fd == -1) { - printf("failed to open \"%s\" %s\n", filename, strerror(errno)); + 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 +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; +} + +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(); + } + } + + return true; +} + +bool ZipModeImage::GeneratePatches(ZipModeImage* tgt_image, ZipModeImage* src_image, + const std::string& patch_name) { + // 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(); + + printf("Construct patches for %zu chunks...\n", tgt_image->NumOfChunks()); + std::vector<std::vector<uint8_t>> patch_data(tgt_image->NumOfChunks()); + + saidx_t* bsdiff_cache = nullptr; + size_t i = 0; + for (auto& tgt_chunk : *tgt_image) { + 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; + + if (!tgt_chunk.MakePatch(src_ref, &patch_data[i], 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[i].size(), + tgt_chunk.GetRawDataLength()); + i++; + } + free(bsdiff_cache); + + 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 tgt_image->WritePatchDataToFd(patch_data, patch_fd); +} + +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; + + // 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(ImageModeImage* tgt_image, ImageModeImage* src_image, + const std::vector<uint8_t>& bonus_data, const std::string& patch_name); +}; + +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 +1000,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. @@ -693,28 +1036,31 @@ static bool ReadImage(const char* filename, std::vector<ImageChunk>* chunks, continue; } - ImageChunk body(CHUNK_DEFLATE, pos, img, raw_data_len); + // The footer contains the size of the uncompressed data. Double-check to make sure that it + // matches the size of the data we got when we actually did the decompression. + size_t footer_index = pos + raw_data_len + GZIP_FOOTER_LEN - 4; + if (sz - footer_index < 4) { + printf("Warning: invalid footer position; treating as a nomal chunk\n"); + continue; + } + 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, &file_content_, raw_data_len); uncompressed_data.resize(uncompressed_len); body.SetUncompressedData(std::move(uncompressed_data)); - chunks->push_back(body); + chunks_.push_back(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; - - // The footer (that we just skipped over) contains the size of - // the uncompressed data. Double-check to make sure that it - // matches the size of the data we got when we actually did - // the decompression. - size_t footer_size = get_unaligned<uint32_t>(img->data() + pos - 4); - if (footer_size != body.DataLengthForPatch()) { - printf("Error: footer size %zu != decompressed size %zu\n", footer_size, - body.GetRawDataLength()); - return false; - } } else { // Use a normal chunk to take all the contents until the next gzip chunk (or EOF); we expect // the number of chunks to be small (5 for typical boot and recovery images). @@ -723,12 +1069,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; } @@ -737,346 +1083,178 @@ 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)); +// 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; } - 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; + for (size_t i = 0; i < tgt_image->NumOfChunks(); ++i) { + if (tgt_image->Get(i)->GetType() != src_image->Get(i)->GetType()) { + printf("source and target don't have same chunk structure! (chunk %zu)\n", i); + tgt_image->DumpChunks(); + src_image->DumpChunks(); + 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; - } + for (size_t i = 0; i < tgt_image->NumOfChunks(); ++i) { + auto& tgt_chunk = *tgt_image->Get(i); + auto& src_chunk = *src_image->Get(i); + if (tgt_chunk.GetType() != CHUNK_DEFLATE) { + continue; + } - 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; + // Confirm that we can recompress the data and get exactly the same bits as are in the + // input target image. + if (!tgt_chunk.ReconstructDeflateChunk()) { + 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(); + continue; + } + + // If two deflate chunks are identical treat them as normal chunks. + if (tgt_chunk == src_chunk) { + 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(ImageModeImage* tgt_image, ImageModeImage* src_image, + const std::vector<uint8_t>& bonus_data, + const std::string& patch_name) { + printf("Construct patches for %zu chunks...\n", tgt_image->NumOfChunks()); + std::vector<std::vector<uint8_t>> patch_data(tgt_image->NumOfChunks()); + + for (size_t i = 0; i < tgt_image->NumOfChunks(); i++) { + auto& tgt_chunk = *tgt_image->Get(i); + auto& src_chunk = *src_image->Get(i); + + if (i == 1 && !bonus_data.empty()) { + printf(" using %zu bytes of bonus data for chunk %zu\n", bonus_data.size(), i); + src_chunk.SetBonusData(bonus_data); } - if (merged_last != cur) { - chunks->at(merged_last) = std::move(chunks->at(cur)); + if (!tgt_chunk.MakePatch(src_chunk, &patch_data[i], 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[i].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]; - } + 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 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(); - } + return tgt_image->WritePatchDataToFd(patch_data, 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(); - } - } - } - - // 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 (!tgt_image.Initialize(argv[optind + 1])) { 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 (!ImageModeImage::CheckAndProcessChunks(&tgt_image, &src_image)) { + 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, bonus_data, argv[optind + 2])) { + return 1; } } 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..4cbad4cfe 100644 --- a/error_code.h +++ b/error_code.h @@ -68,6 +68,8 @@ enum UncryptErrorCode { kUncryptFileCloseError, kUncryptFileRenameError, kUncryptPackageMissingError, + kUncryptRealpathFindError, + kUncryptBlockDeviceFindError, }; #endif // _ERROR_CODE_H_ diff --git a/install.cpp b/install.cpp index a1f2e4fbd..7fbf5c01f 100644 --- a/install.cpp +++ b/install.cpp @@ -27,6 +27,7 @@ #include <unistd.h> #include <algorithm> +#include <atomic> #include <chrono> #include <condition_variable> #include <functional> @@ -65,18 +66,14 @@ static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25; static std::condition_variable finish_log_temperature; // This function parses and returns the build.version.incremental -static int parse_build_number(const std::string& str) { +static std::string parse_build_number(const std::string& str) { size_t pos = str.find('='); if (pos != std::string::npos) { - std::string num_string = android::base::Trim(str.substr(pos+1)); - int build_number; - if (android::base::ParseInt(num_string.c_str(), &build_number, 0)) { - return build_number; - } + return android::base::Trim(str.substr(pos+1)); } LOG(ERROR) << "Failed to parse build number in " << str; - return -1; + return ""; } bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) { @@ -113,14 +110,14 @@ static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::stri for (const std::string& line : lines) { std::string str = android::base::Trim(line); if (android::base::StartsWith(str, "pre-build-incremental")) { - int source_build = parse_build_number(str); - if (source_build != -1) { - log_buffer->push_back(android::base::StringPrintf("source_build: %d", source_build)); + std::string source_build = parse_build_number(str); + if (!source_build.empty()) { + log_buffer->push_back("source_build: " + source_build); } } else if (android::base::StartsWith(str, "post-build-incremental")) { - int target_build = parse_build_number(str); - if (target_build != -1) { - log_buffer->push_back(android::base::StringPrintf("target_build: %d", target_build)); + std::string target_build = parse_build_number(str); + if (!target_build.empty()) { + log_buffer->push_back("target_build: " + target_build); } } } @@ -268,7 +265,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; @@ -294,11 +291,12 @@ int update_binary_command(const std::string& package, ZipArchiveHandle zip, } #endif // !AB_OTA_UPDATER -static void log_max_temperature(int* max_temperature) { +static void log_max_temperature(int* max_temperature, const std::atomic<bool>& logger_finished) { CHECK(max_temperature != nullptr); std::mutex mtx; std::unique_lock<std::mutex> lck(mtx); - while (finish_log_temperature.wait_for(lck, 20s) == std::cv_status::timeout) { + while (!logger_finished.load() && + finish_log_temperature.wait_for(lck, 20s) == std::cv_status::timeout) { *max_temperature = std::max(*max_temperature, GetMaxValueFromThermalZone()); } } @@ -403,7 +401,8 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b } close(pipefd[1]); - std::thread temperature_logger(log_max_temperature, max_temperature); + std::atomic<bool> logger_finished(false); + std::thread temperature_logger(log_max_temperature, max_temperature, std::ref(logger_finished)); *wipe_cache = false; bool retry_update = false; @@ -467,6 +466,7 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b int status; waitpid(pid, &status, 0); + logger_finished.store(true); finish_log_temperature.notify_one(); temperature_logger.join(); 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/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/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 852f1e862..9abb9341c 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -179,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 @@ -250,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]); @@ -419,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() { @@ -478,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 { @@ -552,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); @@ -580,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; @@ -596,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)) { @@ -1593,15 +1591,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) { @@ -16,162 +16,166 @@ #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 <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; + } + + const MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point); + if (mv != nullptr) { + // Volume is already mounted. + return 0; + } - MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point); - if (mv) { - // 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) { @@ -192,138 +196,116 @@ static ssize_t get_file_size(int fd, uint64_t reserve_len) { } 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) { - android::base::unique_fd fd(open(v->blk_device, O_RDONLY)); - if (fd < 0) { - PLOG(ERROR) << "get_file_size: failed to open " << v->blk_device; - 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; - } - } - int result; - if (strcmp(v->fs_type, "ext4") == 0) { - static constexpr int block_size = 4096; - int raid_stride = v->logical_blk_size / block_size; - int raid_stripe_width = v->erase_blk_size / block_size; - - // 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 / block_size; - } - - const char* mke2fs_argv[] = { "/sbin/mke2fs_static", - "-F", - "-t", - "ext4", - "-b", - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr }; - - int i = 5; - std::string block_size_str = std::to_string(block_size); - mke2fs_argv[i++] = block_size_str.c_str(); - - std::string ext_args; - if (v->erase_blk_size != 0 && v->logical_blk_size != 0) { - ext_args = android::base::StringPrintf("stride=%d,stripe-width=%d", raid_stride, - raid_stripe_width); - mke2fs_argv[i++] = "-E"; - mke2fs_argv[i++] = ext_args.c_str(); - } - - mke2fs_argv[i++] = v->blk_device; - - std::string size_str = std::to_string(length / block_size); - if (length != 0) { - mke2fs_argv[i++] = size_str.c_str(); - } - - result = exec_cmd(mke2fs_argv[0], const_cast<char**>(mke2fs_argv)); - if (result == 0 && directory != nullptr) { - const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static", - "-e", - "-S", - "/file_contexts", - "-f", - directory, - "-a", - volume, - v->blk_device, - nullptr }; - - result = exec_cmd(e2fsdroid_argv[0], const_cast<char**>(e2fsdroid_argv)); - } - } else { /* Has to be f2fs because we checked earlier. */ - 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() { @@ -341,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 a7d9c9f4b..8f792f162 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -45,9 +45,9 @@ // Return the current time as a double (including fractions of a second). static double now() { - struct timeval tv; - gettimeofday(&tv, nullptr); - return tv.tv_sec + tv.tv_usec / 1000000.0; + struct timeval tv; + gettimeofday(&tv, nullptr); + return tv.tv_sec + tv.tv_usec / 1000000.0; } ScreenRecoveryUI::ScreenRecoveryUI() @@ -82,25 +82,30 @@ ScreenRecoveryUI::ScreenRecoveryUI() max_stage(-1), updateMutex(PTHREAD_MUTEX_INITIALIZER) {} -GRSurface* ScreenRecoveryUI::GetCurrentFrame() { - if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { - return intro_done ? loopFrames[current_frame] : introFrames[current_frame]; - } - return error_icon; -} - -GRSurface* ScreenRecoveryUI::GetCurrentText() { - switch (currentIcon) { - case ERASING: return erasing_text; - case ERROR: return error_text; - case INSTALLING_UPDATE: return installing_text; - case NO_COMMAND: return no_command_text; - case NONE: abort(); - } +GRSurface* ScreenRecoveryUI::GetCurrentFrame() const { + if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { + return intro_done ? loopFrames[current_frame] : introFrames[current_frame]; + } + return error_icon; +} + +GRSurface* ScreenRecoveryUI::GetCurrentText() const { + switch (currentIcon) { + case ERASING: + return erasing_text; + case ERROR: + return error_text; + case INSTALLING_UPDATE: + return installing_text; + case NO_COMMAND: + return no_command_text; + case NONE: + abort(); + } } int ScreenRecoveryUI::PixelsFromDp(int dp) const { - return dp * density_; + return dp * density_; } // Here's the intended layout: @@ -127,17 +132,16 @@ static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = { { 52, 112, }, // LANDSCAPE_LARGE }; -int ScreenRecoveryUI::GetAnimationBaseline() { - return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - - gr_get_height(loopFrames[0]); +int ScreenRecoveryUI::GetAnimationBaseline() const { + return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - gr_get_height(loopFrames[0]); } -int ScreenRecoveryUI::GetTextBaseline() { - return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) - - gr_get_height(installing_text); +int ScreenRecoveryUI::GetTextBaseline() const { + return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) - + gr_get_height(installing_text); } -int ScreenRecoveryUI::GetProgressBaseline() { +int ScreenRecoveryUI::GetProgressBaseline() const { int elements_sum = gr_get_height(loopFrames[0]) + PixelsFromDp(kLayouts[layout_][ICON]) + gr_get_height(installing_text) + PixelsFromDp(kLayouts[layout_][TEXT]) + gr_get_height(progressBarFill); @@ -148,34 +152,33 @@ int ScreenRecoveryUI::GetProgressBaseline() { // Clear the screen and draw the currently selected background icon (if any). // Should only be called with updateMutex locked. void ScreenRecoveryUI::draw_background_locked() { - pagesIdentical = false; - gr_color(0, 0, 0, 255); - gr_clear(); - - if (currentIcon != NONE) { - if (max_stage != -1) { - int stage_height = gr_get_height(stageMarkerEmpty); - int stage_width = gr_get_width(stageMarkerEmpty); - int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2; - int y = gr_fb_height() - stage_height; - for (int i = 0; i < max_stage; ++i) { - GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty; - gr_blit(stage_surface, 0, 0, stage_width, stage_height, x, y); - x += stage_width; - } - } + pagesIdentical = false; + gr_color(0, 0, 0, 255); + gr_clear(); - GRSurface* text_surface = GetCurrentText(); - int text_x = (gr_fb_width() - gr_get_width(text_surface)) / 2; - int text_y = GetTextBaseline(); - gr_color(255, 255, 255, 255); - gr_texticon(text_x, text_y, text_surface); + if (currentIcon != NONE) { + if (max_stage != -1) { + int stage_height = gr_get_height(stageMarkerEmpty); + int stage_width = gr_get_width(stageMarkerEmpty); + int x = (gr_fb_width() - max_stage * gr_get_width(stageMarkerEmpty)) / 2; + int y = gr_fb_height() - stage_height; + for (int i = 0; i < max_stage; ++i) { + GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty; + gr_blit(stage_surface, 0, 0, stage_width, stage_height, x, y); + x += stage_width; + } } + + GRSurface* text_surface = GetCurrentText(); + int text_x = (gr_fb_width() - gr_get_width(text_surface)) / 2; + int text_y = GetTextBaseline(); + gr_color(255, 255, 255, 255); + gr_texticon(text_x, text_y, text_surface); + } } -// Draws the animation and progress bar (if any) on the screen. -// Does not flip pages. -// Should only be called with updateMutex locked. +// Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be +// called with updateMutex locked. void ScreenRecoveryUI::draw_foreground_locked() { if (currentIcon != NONE) { GRSurface* frame = GetCurrentFrame(); @@ -223,67 +226,67 @@ void ScreenRecoveryUI::draw_foreground_locked() { } } -void ScreenRecoveryUI::SetColor(UIElement e) { - switch (e) { - case INFO: - gr_color(249, 194, 0, 255); - break; - case HEADER: - gr_color(247, 0, 6, 255); - break; - case MENU: - case MENU_SEL_BG: - gr_color(0, 106, 157, 255); - break; - case MENU_SEL_BG_ACTIVE: - gr_color(0, 156, 100, 255); - break; - case MENU_SEL_FG: - gr_color(255, 255, 255, 255); - break; - case LOG: - gr_color(196, 196, 196, 255); - break; - case TEXT_FILL: - gr_color(0, 0, 0, 160); - break; - default: - gr_color(255, 255, 255, 255); - break; - } +void ScreenRecoveryUI::SetColor(UIElement e) const { + switch (e) { + case INFO: + gr_color(249, 194, 0, 255); + break; + case HEADER: + gr_color(247, 0, 6, 255); + break; + case MENU: + case MENU_SEL_BG: + gr_color(0, 106, 157, 255); + break; + case MENU_SEL_BG_ACTIVE: + gr_color(0, 156, 100, 255); + break; + case MENU_SEL_FG: + gr_color(255, 255, 255, 255); + break; + case LOG: + gr_color(196, 196, 196, 255); + break; + case TEXT_FILL: + gr_color(0, 0, 0, 160); + break; + default: + gr_color(255, 255, 255, 255); + break; + } } -void ScreenRecoveryUI::DrawHorizontalRule(int* y) { - SetColor(MENU); - *y += 4; - gr_fill(0, *y, gr_fb_width(), *y + 2); - *y += 4; +int ScreenRecoveryUI::DrawHorizontalRule(int y) const { + gr_fill(0, y + 4, gr_fb_width(), y + 6); + return 8; } void ScreenRecoveryUI::DrawHighlightBar(int x, int y, int width, int height) const { - gr_fill(x, y, x + width, y + height); + gr_fill(x, y, x + width, y + height); } -void ScreenRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) const { - gr_text(gr_sys_font(), x, *y, line, bold); - *y += char_height_ + 4; +int ScreenRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const { + gr_text(gr_sys_font(), x, y, line, bold); + return char_height_ + 4; } -void ScreenRecoveryUI::DrawTextLines(int x, int* y, const char* const* lines) const { - for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { - DrawTextLine(x, y, lines[i], false); - } +int ScreenRecoveryUI::DrawTextLines(int x, int y, const char* const* lines) const { + int offset = 0; + for (size_t i = 0; lines != nullptr && lines[i] != nullptr; ++i) { + offset += DrawTextLine(x, y + offset, lines[i], false); + } + return offset; } static const char* REGULAR_HELP[] = { - "Use volume up/down and power.", - NULL + "Use volume up/down and power.", + NULL }; static const char* LONG_PRESS_HELP[] = { - "Any button cycles highlight.", - "Long-press activates.", - NULL + "Any button cycles highlight.", + "Long-press activates.", + NULL }; // Redraws everything on the screen. Does not flip pages. Should only be called with updateMutex @@ -294,29 +297,29 @@ void ScreenRecoveryUI::draw_screen_locked() { draw_foreground_locked(); return; } + gr_color(0, 0, 0, 255); gr_clear(); - static constexpr int TEXT_INDENT = 4; - int x = TEXT_INDENT + kMarginWidth; int y = kMarginHeight; if (show_menu) { - std::string recovery_fingerprint = - android::base::GetProperty("ro.bootimage.build.fingerprint", ""); + static constexpr int kMenuIndent = 4; + int x = kMarginWidth + kMenuIndent; SetColor(INFO); - DrawTextLine(x, &y, "Android Recovery", true); + y += DrawTextLine(x, y, "Android Recovery", true); + std::string recovery_fingerprint = + android::base::GetProperty("ro.bootimage.build.fingerprint", ""); for (const auto& chunk : android::base::Split(recovery_fingerprint, ":")) { - DrawTextLine(x, &y, chunk.c_str(), false); + y += DrawTextLine(x, y, chunk.c_str(), false); } - DrawTextLines(x, &y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); + y += DrawTextLines(x, y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); SetColor(HEADER); - DrawTextLines(x, &y, menu_headers_); + y += DrawTextLines(x, y, menu_headers_); SetColor(MENU); - DrawHorizontalRule(&y); - y += 4; + y += DrawHorizontalRule(y) + 4; for (int i = 0; i < menu_items; ++i) { if (i == menu_sel) { // Draw the highlight bar. @@ -324,13 +327,13 @@ void ScreenRecoveryUI::draw_screen_locked() { DrawHighlightBar(0, y - 2, gr_fb_width(), char_height_ + 4); // Bold white text for the selected item. SetColor(MENU_SEL_FG); - DrawTextLine(x, &y, menu_[i], true); + y += DrawTextLine(x, y, menu_[i], true); SetColor(MENU); } else { - DrawTextLine(x, &y, menu_[i], false); + y += DrawTextLine(x, y, menu_[i], false); } } - DrawHorizontalRule(&y); + y += DrawHorizontalRule(y); } // Display from the bottom up, until we hit the top of the screen, the bottom of the menu, or @@ -338,10 +341,9 @@ void ScreenRecoveryUI::draw_screen_locked() { SetColor(LOG); int row = (text_top_ + text_rows_ - 1) % text_rows_; size_t count = 0; - for (int ty = gr_fb_height() - kMarginHeight - char_height_; - ty >= y && count < text_rows_; ty -= char_height_, ++count) { - int temp_y = ty; - DrawTextLine(x, &temp_y, text_[row], false); + for (int ty = gr_fb_height() - kMarginHeight - char_height_; ty >= y && count < text_rows_; + ty -= char_height_, ++count) { + DrawTextLine(kMarginWidth, ty, text_[row], false); --row; if (row < 0) row = text_rows_ - 1; } @@ -350,81 +352,81 @@ void ScreenRecoveryUI::draw_screen_locked() { // Redraw everything on the screen and flip the screen (make it visible). // Should only be called with updateMutex locked. void ScreenRecoveryUI::update_screen_locked() { - draw_screen_locked(); - gr_flip(); + draw_screen_locked(); + gr_flip(); } // Updates only the progress bar, if possible, otherwise redraws the screen. // Should only be called with updateMutex locked. void ScreenRecoveryUI::update_progress_locked() { - if (show_text || !pagesIdentical) { - draw_screen_locked(); // Must redraw the whole screen - pagesIdentical = true; - } else { - draw_foreground_locked(); // Draw only the progress bar and overlays - } - gr_flip(); + if (show_text || !pagesIdentical) { + draw_screen_locked(); // Must redraw the whole screen + pagesIdentical = true; + } else { + draw_foreground_locked(); // Draw only the progress bar and overlays + } + gr_flip(); } // Keeps the progress bar updated, even when the process is otherwise busy. void* ScreenRecoveryUI::ProgressThreadStartRoutine(void* data) { - reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop(); - return nullptr; + reinterpret_cast<ScreenRecoveryUI*>(data)->ProgressThreadLoop(); + return nullptr; } void ScreenRecoveryUI::ProgressThreadLoop() { - double interval = 1.0 / animation_fps; - while (true) { - double start = now(); - pthread_mutex_lock(&updateMutex); - - bool redraw = false; - - // update the installation animation, if active - // skip this if we have a text overlay (too expensive to update) - if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) { - if (!intro_done) { - if (current_frame == intro_frames - 1) { - intro_done = true; - current_frame = 0; - } else { - ++current_frame; - } - } else { - current_frame = (current_frame + 1) % loop_frames; - } - - redraw = true; - } + double interval = 1.0 / animation_fps; + while (true) { + double start = now(); + pthread_mutex_lock(&updateMutex); + + bool redraw = false; - // move the progress bar forward on timed intervals, if configured - int duration = progressScopeDuration; - if (progressBarType == DETERMINATE && duration > 0) { - double elapsed = now() - progressScopeTime; - float p = 1.0 * elapsed / duration; - if (p > 1.0) p = 1.0; - if (p > progress) { - progress = p; - redraw = true; - } + // update the installation animation, if active + // skip this if we have a text overlay (too expensive to update) + if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) { + if (!intro_done) { + if (current_frame == intro_frames - 1) { + intro_done = true; + current_frame = 0; + } else { + ++current_frame; } + } else { + current_frame = (current_frame + 1) % loop_frames; + } - if (redraw) update_progress_locked(); + redraw = true; + } - pthread_mutex_unlock(&updateMutex); - double end = now(); - // minimum of 20ms delay between frames - double delay = interval - (end-start); - if (delay < 0.02) delay = 0.02; - usleep(static_cast<useconds_t>(delay * 1000000)); + // move the progress bar forward on timed intervals, if configured + int duration = progressScopeDuration; + if (progressBarType == DETERMINATE && duration > 0) { + double elapsed = now() - progressScopeTime; + float p = 1.0 * elapsed / duration; + if (p > 1.0) p = 1.0; + if (p > progress) { + progress = p; + redraw = true; + } } + + if (redraw) update_progress_locked(); + + pthread_mutex_unlock(&updateMutex); + double end = now(); + // minimum of 20ms delay between frames + double delay = interval - (end - start); + if (delay < 0.02) delay = 0.02; + usleep(static_cast<useconds_t>(delay * 1000000)); + } } void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { - int result = res_create_display_surface(filename, surface); - if (result < 0) { - LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")"; - } + int result = res_create_display_surface(filename, surface); + if (result < 0) { + LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")"; + } } void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) { @@ -435,22 +437,22 @@ void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** sur } static char** Alloc2d(size_t rows, size_t cols) { - char** result = new char*[rows]; - for (size_t i = 0; i < rows; ++i) { - result[i] = new char[cols]; - memset(result[i], 0, cols); - } - return result; + char** result = new char*[rows]; + for (size_t i = 0; i < rows; ++i) { + result[i] = new char[cols]; + memset(result[i], 0, cols); + } + return result; } // Choose the right background string to display during update. void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) { - if (security_update) { - LoadLocalizedBitmap("installing_security_text", &installing_text); - } else { - LoadLocalizedBitmap("installing_text", &installing_text); - } - Redraw(); + if (security_update) { + LoadLocalizedBitmap("installing_security_text", &installing_text); + } else { + LoadLocalizedBitmap("installing_text", &installing_text); + } + Redraw(); } bool ScreenRecoveryUI::InitTextParams() { @@ -506,309 +508,309 @@ bool ScreenRecoveryUI::Init(const std::string& locale) { } void ScreenRecoveryUI::LoadAnimation() { - std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir); - dirent* de; - std::vector<std::string> intro_frame_names; - std::vector<std::string> loop_frame_names; - - while ((de = readdir(dir.get())) != nullptr) { - int value, num_chars; - if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) { - intro_frame_names.emplace_back(de->d_name, num_chars); - } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) { - loop_frame_names.emplace_back(de->d_name, num_chars); - } + std::unique_ptr<DIR, decltype(&closedir)> dir(opendir("/res/images"), closedir); + dirent* de; + std::vector<std::string> intro_frame_names; + std::vector<std::string> loop_frame_names; + + while ((de = readdir(dir.get())) != nullptr) { + int value, num_chars; + if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) { + intro_frame_names.emplace_back(de->d_name, num_chars); + } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) { + loop_frame_names.emplace_back(de->d_name, num_chars); } + } - intro_frames = intro_frame_names.size(); - loop_frames = loop_frame_names.size(); + intro_frames = intro_frame_names.size(); + loop_frames = loop_frame_names.size(); - // It's okay to not have an intro. - if (intro_frames == 0) intro_done = true; - // But you must have an animation. - if (loop_frames == 0) abort(); + // It's okay to not have an intro. + if (intro_frames == 0) intro_done = true; + // But you must have an animation. + if (loop_frames == 0) abort(); - std::sort(intro_frame_names.begin(), intro_frame_names.end()); - std::sort(loop_frame_names.begin(), loop_frame_names.end()); + std::sort(intro_frame_names.begin(), intro_frame_names.end()); + std::sort(loop_frame_names.begin(), loop_frame_names.end()); - introFrames = new GRSurface*[intro_frames]; - for (size_t i = 0; i < intro_frames; i++) { - LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]); - } + introFrames = new GRSurface*[intro_frames]; + for (size_t i = 0; i < intro_frames; i++) { + LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]); + } - loopFrames = new GRSurface*[loop_frames]; - for (size_t i = 0; i < loop_frames; i++) { - LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]); - } + loopFrames = new GRSurface*[loop_frames]; + for (size_t i = 0; i < loop_frames; i++) { + LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]); + } } void ScreenRecoveryUI::SetBackground(Icon icon) { - pthread_mutex_lock(&updateMutex); + pthread_mutex_lock(&updateMutex); - currentIcon = icon; - update_screen_locked(); + currentIcon = icon; + update_screen_locked(); - pthread_mutex_unlock(&updateMutex); + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::SetProgressType(ProgressType type) { - pthread_mutex_lock(&updateMutex); - if (progressBarType != type) { - progressBarType = type; - } - progressScopeStart = 0; - progressScopeSize = 0; - progress = 0; - update_progress_locked(); - pthread_mutex_unlock(&updateMutex); + pthread_mutex_lock(&updateMutex); + if (progressBarType != type) { + progressBarType = type; + } + progressScopeStart = 0; + progressScopeSize = 0; + progress = 0; + update_progress_locked(); + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::ShowProgress(float portion, float seconds) { - pthread_mutex_lock(&updateMutex); - progressBarType = DETERMINATE; - progressScopeStart += progressScopeSize; - progressScopeSize = portion; - progressScopeTime = now(); - progressScopeDuration = seconds; - progress = 0; - update_progress_locked(); - pthread_mutex_unlock(&updateMutex); + pthread_mutex_lock(&updateMutex); + progressBarType = DETERMINATE; + progressScopeStart += progressScopeSize; + progressScopeSize = portion; + progressScopeTime = now(); + progressScopeDuration = seconds; + progress = 0; + update_progress_locked(); + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::SetProgress(float fraction) { - pthread_mutex_lock(&updateMutex); - if (fraction < 0.0) fraction = 0.0; - if (fraction > 1.0) fraction = 1.0; - if (progressBarType == DETERMINATE && fraction > progress) { - // Skip updates that aren't visibly different. - int width = gr_get_width(progressBarEmpty); - float scale = width * progressScopeSize; - if ((int) (progress * scale) != (int) (fraction * scale)) { - progress = fraction; - update_progress_locked(); - } + pthread_mutex_lock(&updateMutex); + if (fraction < 0.0) fraction = 0.0; + if (fraction > 1.0) fraction = 1.0; + if (progressBarType == DETERMINATE && fraction > progress) { + // Skip updates that aren't visibly different. + int width = gr_get_width(progressBarEmpty); + float scale = width * progressScopeSize; + if ((int)(progress * scale) != (int)(fraction * scale)) { + progress = fraction; + update_progress_locked(); } - pthread_mutex_unlock(&updateMutex); + } + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::SetStage(int current, int max) { - pthread_mutex_lock(&updateMutex); - stage = current; - max_stage = max; - pthread_mutex_unlock(&updateMutex); + pthread_mutex_lock(&updateMutex); + stage = current; + max_stage = max; + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { - std::string str; - android::base::StringAppendV(&str, fmt, ap); + std::string str; + android::base::StringAppendV(&str, fmt, ap); - if (copy_to_stdout) { - fputs(str.c_str(), stdout); - } + if (copy_to_stdout) { + fputs(str.c_str(), stdout); + } - pthread_mutex_lock(&updateMutex); - if (text_rows_ > 0 && text_cols_ > 0) { - for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { - if (*ptr == '\n' || text_col_ >= text_cols_) { - text_[text_row_][text_col_] = '\0'; - text_col_ = 0; - text_row_ = (text_row_ + 1) % text_rows_; - if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; - } - if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; - } + pthread_mutex_lock(&updateMutex); + if (text_rows_ > 0 && text_cols_ > 0) { + for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { + if (*ptr == '\n' || text_col_ >= text_cols_) { text_[text_row_][text_col_] = '\0'; - update_screen_locked(); + text_col_ = 0; + text_row_ = (text_row_ + 1) % text_rows_; + if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; + } + if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; } - pthread_mutex_unlock(&updateMutex); + text_[text_row_][text_col_] = '\0'; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::Print(const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - PrintV(fmt, true, ap); - va_end(ap); + va_list ap; + va_start(ap, fmt); + PrintV(fmt, true, ap); + va_end(ap); } void ScreenRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - PrintV(fmt, false, ap); - va_end(ap); + va_list ap; + va_start(ap, fmt); + PrintV(fmt, false, ap); + va_end(ap); } void ScreenRecoveryUI::PutChar(char ch) { - pthread_mutex_lock(&updateMutex); - if (ch != '\n') text_[text_row_][text_col_++] = ch; - if (ch == '\n' || text_col_ >= text_cols_) { - text_col_ = 0; - ++text_row_; + pthread_mutex_lock(&updateMutex); + if (ch != '\n') text_[text_row_][text_col_++] = ch; + if (ch == '\n' || text_col_ >= text_cols_) { + text_col_ = 0; + ++text_row_; - if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; - } - pthread_mutex_unlock(&updateMutex); + if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; + } + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::ClearText() { - pthread_mutex_lock(&updateMutex); - text_col_ = 0; - text_row_ = 0; - text_top_ = 1; - for (size_t i = 0; i < text_rows_; ++i) { - memset(text_[i], 0, text_cols_ + 1); - } - pthread_mutex_unlock(&updateMutex); + pthread_mutex_lock(&updateMutex); + text_col_ = 0; + text_row_ = 0; + text_top_ = 1; + for (size_t i = 0; i < text_rows_; ++i) { + memset(text_[i], 0, text_cols_ + 1); + } + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::ShowFile(FILE* fp) { - std::vector<off_t> offsets; - offsets.push_back(ftello(fp)); - ClearText(); - - struct stat sb; - fstat(fileno(fp), &sb); - - bool show_prompt = false; - while (true) { - if (show_prompt) { - PrintOnScreenOnly("--(%d%% of %d bytes)--", - static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))), - static_cast<int>(sb.st_size)); - Redraw(); - while (show_prompt) { - show_prompt = false; - int key = WaitKey(); - if (key == KEY_POWER || key == KEY_ENTER) { - return; - } else if (key == KEY_UP || key == KEY_VOLUMEUP) { - if (offsets.size() <= 1) { - show_prompt = true; - } else { - offsets.pop_back(); - fseek(fp, offsets.back(), SEEK_SET); - } - } else { - if (feof(fp)) { - return; - } - offsets.push_back(ftello(fp)); - } - } - ClearText(); - } - - int ch = getc(fp); - if (ch == EOF) { - while (text_row_ < text_rows_ - 1) PutChar('\n'); + std::vector<off_t> offsets; + offsets.push_back(ftello(fp)); + ClearText(); + + struct stat sb; + fstat(fileno(fp), &sb); + + bool show_prompt = false; + while (true) { + if (show_prompt) { + PrintOnScreenOnly("--(%d%% of %d bytes)--", + static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))), + static_cast<int>(sb.st_size)); + Redraw(); + while (show_prompt) { + show_prompt = false; + int key = WaitKey(); + if (key == KEY_POWER || key == KEY_ENTER) { + return; + } else if (key == KEY_UP || key == KEY_VOLUMEUP) { + if (offsets.size() <= 1) { show_prompt = true; + } else { + offsets.pop_back(); + fseek(fp, offsets.back(), SEEK_SET); + } } else { - PutChar(ch); - if (text_col_ == 0 && text_row_ >= text_rows_ - 1) { - show_prompt = true; - } + if (feof(fp)) { + return; + } + offsets.push_back(ftello(fp)); } + } + ClearText(); } + + int ch = getc(fp); + if (ch == EOF) { + while (text_row_ < text_rows_ - 1) PutChar('\n'); + show_prompt = true; + } else { + PutChar(ch); + if (text_col_ == 0 && text_row_ >= text_rows_ - 1) { + show_prompt = true; + } + } + } } void ScreenRecoveryUI::ShowFile(const char* filename) { - FILE* fp = fopen_path(filename, "re"); - if (fp == nullptr) { - Print(" Unable to open %s: %s\n", filename, strerror(errno)); - return; - } + FILE* fp = fopen_path(filename, "re"); + if (fp == nullptr) { + Print(" Unable to open %s: %s\n", filename, strerror(errno)); + return; + } - char** old_text = text_; - size_t old_text_col = text_col_; - size_t old_text_row = text_row_; - size_t old_text_top = text_top_; + char** old_text = text_; + size_t old_text_col = text_col_; + size_t old_text_row = text_row_; + size_t old_text_top = text_top_; - // Swap in the alternate screen and clear it. - text_ = file_viewer_text_; - ClearText(); + // Swap in the alternate screen and clear it. + text_ = file_viewer_text_; + ClearText(); - ShowFile(fp); - fclose(fp); + ShowFile(fp); + fclose(fp); - text_ = old_text; - text_col_ = old_text_col; - text_row_ = old_text_row; - text_top_ = old_text_top; + text_ = old_text; + text_col_ = old_text_col; + text_row_ = old_text_row; + text_top_ = old_text_top; } -void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const * items, +void ScreenRecoveryUI::StartMenu(const char* const* headers, const char* const* items, int initial_selection) { - pthread_mutex_lock(&updateMutex); - if (text_rows_ > 0 && text_cols_ > 0) { - menu_headers_ = headers; - size_t i = 0; - for (; i < text_rows_ && items[i] != nullptr; ++i) { - strncpy(menu_[i], items[i], text_cols_ - 1); - menu_[i][text_cols_ - 1] = '\0'; - } - menu_items = i; - show_menu = true; - menu_sel = initial_selection; - update_screen_locked(); + pthread_mutex_lock(&updateMutex); + if (text_rows_ > 0 && text_cols_ > 0) { + menu_headers_ = headers; + size_t i = 0; + for (; i < text_rows_ && items[i] != nullptr; ++i) { + strncpy(menu_[i], items[i], text_cols_ - 1); + menu_[i][text_cols_ - 1] = '\0'; } - pthread_mutex_unlock(&updateMutex); + menu_items = i; + show_menu = true; + menu_sel = initial_selection; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); } int ScreenRecoveryUI::SelectMenu(int sel) { - pthread_mutex_lock(&updateMutex); - if (show_menu) { - int old_sel = menu_sel; - menu_sel = sel; + pthread_mutex_lock(&updateMutex); + if (show_menu) { + int old_sel = menu_sel; + menu_sel = sel; - // Wrap at top and bottom. - if (menu_sel < 0) menu_sel = menu_items - 1; - if (menu_sel >= menu_items) menu_sel = 0; + // Wrap at top and bottom. + if (menu_sel < 0) menu_sel = menu_items - 1; + if (menu_sel >= menu_items) menu_sel = 0; - sel = menu_sel; - if (menu_sel != old_sel) update_screen_locked(); - } - pthread_mutex_unlock(&updateMutex); - return sel; + sel = menu_sel; + if (menu_sel != old_sel) update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); + return sel; } void ScreenRecoveryUI::EndMenu() { - pthread_mutex_lock(&updateMutex); - if (show_menu && text_rows_ > 0 && text_cols_ > 0) { - show_menu = false; - update_screen_locked(); - } - pthread_mutex_unlock(&updateMutex); + pthread_mutex_lock(&updateMutex); + if (show_menu && text_rows_ > 0 && text_cols_ > 0) { + show_menu = false; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); } bool ScreenRecoveryUI::IsTextVisible() { - pthread_mutex_lock(&updateMutex); - int visible = show_text; - pthread_mutex_unlock(&updateMutex); - return visible; + pthread_mutex_lock(&updateMutex); + int visible = show_text; + pthread_mutex_unlock(&updateMutex); + return visible; } bool ScreenRecoveryUI::WasTextEverVisible() { - pthread_mutex_lock(&updateMutex); - int ever_visible = show_text_ever; - pthread_mutex_unlock(&updateMutex); - return ever_visible; + pthread_mutex_lock(&updateMutex); + int ever_visible = show_text_ever; + pthread_mutex_unlock(&updateMutex); + return ever_visible; } void ScreenRecoveryUI::ShowText(bool visible) { - pthread_mutex_lock(&updateMutex); - show_text = visible; - if (show_text) show_text_ever = true; - update_screen_locked(); - pthread_mutex_unlock(&updateMutex); + pthread_mutex_lock(&updateMutex); + show_text = visible; + if (show_text) show_text_ever = true; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::Redraw() { - pthread_mutex_lock(&updateMutex); - update_screen_locked(); - pthread_mutex_unlock(&updateMutex); + pthread_mutex_lock(&updateMutex); + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); } void ScreenRecoveryUI::KeyLongPress(int) { - // Redraw so that if we're in the menu, the highlight - // will change color to indicate a successful long press. - Redraw(); + // Redraw so that if we're in the menu, the highlight + // will change color to indicate a successful long press. + Redraw(); } diff --git a/screen_ui.h b/screen_ui.h index 2500575ca..8402fac00 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -30,152 +30,163 @@ struct GRSurface; // Implementation of RecoveryUI appropriate for devices with a screen // (shows an icon + a progress bar, text logging, menu, etc.) class ScreenRecoveryUI : public RecoveryUI { - public: - ScreenRecoveryUI(); + public: + ScreenRecoveryUI(); - bool Init(const std::string& locale) override; + bool Init(const std::string& locale) override; - // overall recovery state ("background image") - void SetBackground(Icon icon); - void SetSystemUpdateText(bool security_update); + // overall recovery state ("background image") + void SetBackground(Icon icon) override; + void SetSystemUpdateText(bool security_update) override; - // progress indicator - void SetProgressType(ProgressType type) override; - void ShowProgress(float portion, float seconds) override; - void SetProgress(float fraction) override; + // progress indicator + void SetProgressType(ProgressType type) override; + void ShowProgress(float portion, float seconds) override; + void SetProgress(float fraction) override; - void SetStage(int current, int max) override; + void SetStage(int current, int max) override; - // text log - void ShowText(bool visible) override; - bool IsTextVisible() override; - bool WasTextEverVisible() override; + // text log + void ShowText(bool visible) override; + bool IsTextVisible() override; + bool WasTextEverVisible() override; - // printing messages - void Print(const char* fmt, ...) __printflike(2, 3); - void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3); - void ShowFile(const char* filename); + // printing messages + void Print(const char* fmt, ...) override __printflike(2, 3); + void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3); + void ShowFile(const char* filename) override; - // menu display - void StartMenu(const char* const * headers, const char* const * items, - int initial_selection); - int SelectMenu(int sel); - void EndMenu(); + // menu display + void StartMenu(const char* const* headers, const char* const* items, + int initial_selection) override; + int SelectMenu(int sel) override; + void EndMenu() override; - void KeyLongPress(int); + void KeyLongPress(int) override; - void Redraw(); + void Redraw(); - enum UIElement { - HEADER, MENU, MENU_SEL_BG, MENU_SEL_BG_ACTIVE, MENU_SEL_FG, LOG, TEXT_FILL, INFO - }; - void SetColor(UIElement e); + enum UIElement { + HEADER, + MENU, + MENU_SEL_BG, + MENU_SEL_BG_ACTIVE, + MENU_SEL_FG, + LOG, + TEXT_FILL, + INFO + }; + void SetColor(UIElement e) const; - protected: - // The margin that we don't want to use for showing texts (e.g. round screen, or screen with - // rounded corners). - const int kMarginWidth; - const int kMarginHeight; + protected: + // The margin that we don't want to use for showing texts (e.g. round screen, or screen with + // rounded corners). + const int kMarginWidth; + const int kMarginHeight; - // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi. - const float density_; + // The scale factor from dp to pixels. 1.0 for mdpi, 4.0 for xxxhdpi. + const float density_; - Icon currentIcon; + Icon currentIcon; - // The layout to use. - int layout_; + // The layout to use. + int layout_; - GRSurface* error_icon; + GRSurface* error_icon; - GRSurface* erasing_text; - GRSurface* error_text; - GRSurface* installing_text; - GRSurface* no_command_text; + GRSurface* erasing_text; + GRSurface* error_text; + GRSurface* installing_text; + GRSurface* no_command_text; - GRSurface** introFrames; - GRSurface** loopFrames; + GRSurface** introFrames; + GRSurface** loopFrames; - GRSurface* progressBarEmpty; - GRSurface* progressBarFill; - GRSurface* stageMarkerEmpty; - GRSurface* stageMarkerFill; + GRSurface* progressBarEmpty; + GRSurface* progressBarFill; + GRSurface* stageMarkerEmpty; + GRSurface* stageMarkerFill; - ProgressType progressBarType; + ProgressType progressBarType; - float progressScopeStart, progressScopeSize, progress; - double progressScopeTime, progressScopeDuration; + float progressScopeStart, progressScopeSize, progress; + double progressScopeTime, progressScopeDuration; - // true when both graphics pages are the same (except for the progress bar). - bool pagesIdentical; + // true when both graphics pages are the same (except for the progress bar). + bool pagesIdentical; - size_t text_cols_, text_rows_; + size_t text_cols_, text_rows_; - // Log text overlay, displayed when a magic key is pressed. - char** text_; - size_t text_col_, text_row_, text_top_; + // Log text overlay, displayed when a magic key is pressed. + char** text_; + size_t text_col_, text_row_, text_top_; - bool show_text; - bool show_text_ever; // has show_text ever been true? + bool show_text; + bool show_text_ever; // has show_text ever been true? - char** menu_; - const char* const* menu_headers_; - bool show_menu; - int menu_items, menu_sel; + char** menu_; + const char* const* menu_headers_; + bool show_menu; + int menu_items, menu_sel; - // An alternate text screen, swapped with 'text_' when we're viewing a log file. - char** file_viewer_text_; + // An alternate text screen, swapped with 'text_' when we're viewing a log file. + char** file_viewer_text_; - pthread_t progress_thread_; + pthread_t progress_thread_; - // Number of intro frames and loop frames in the animation. - size_t intro_frames; - size_t loop_frames; + // Number of intro frames and loop frames in the animation. + size_t intro_frames; + size_t loop_frames; - size_t current_frame; - bool intro_done; + size_t current_frame; + bool intro_done; - // Number of frames per sec (default: 30) for both parts of the animation. - int animation_fps; + // Number of frames per sec (default: 30) for both parts of the animation. + int animation_fps; - int stage, max_stage; + int stage, max_stage; - int char_width_; - int char_height_; + int char_width_; + int char_height_; - pthread_mutex_t updateMutex; + pthread_mutex_t updateMutex; - virtual bool InitTextParams(); + virtual bool InitTextParams(); - virtual void draw_background_locked(); - virtual void draw_foreground_locked(); - virtual void draw_screen_locked(); - virtual void update_screen_locked(); - virtual void update_progress_locked(); + virtual void draw_background_locked(); + virtual void draw_foreground_locked(); + virtual void draw_screen_locked(); + virtual void update_screen_locked(); + virtual void update_progress_locked(); - GRSurface* GetCurrentFrame(); - GRSurface* GetCurrentText(); + GRSurface* GetCurrentFrame() const; + GRSurface* GetCurrentText() const; - static void* ProgressThreadStartRoutine(void* data); - void ProgressThreadLoop(); + static void* ProgressThreadStartRoutine(void* data); + void ProgressThreadLoop(); - virtual void ShowFile(FILE*); - virtual void PrintV(const char*, bool, va_list); - void PutChar(char); - void ClearText(); + virtual void ShowFile(FILE*); + virtual void PrintV(const char*, bool, va_list); + void PutChar(char); + void ClearText(); - void LoadAnimation(); - void LoadBitmap(const char* filename, GRSurface** surface); - void LoadLocalizedBitmap(const char* filename, GRSurface** surface); + void LoadAnimation(); + void LoadBitmap(const char* filename, GRSurface** surface); + void LoadLocalizedBitmap(const char* filename, GRSurface** surface); - int PixelsFromDp(int dp) const; - virtual int GetAnimationBaseline(); - virtual int GetProgressBaseline(); - virtual int GetTextBaseline(); + int PixelsFromDp(int dp) const; + virtual int GetAnimationBaseline() const; + virtual int GetProgressBaseline() const; + virtual int GetTextBaseline() const; - virtual void DrawHorizontalRule(int* y); - virtual void DrawHighlightBar(int x, int y, int width, int height) const; - virtual void DrawTextLine(int x, int* y, const char* line, bool bold) const; - void DrawTextLines(int x, int* y, const char* const* lines) const; + // Draws a highlight bar at (x, y) - (x + width, y + height). + virtual void DrawHighlightBar(int x, int y, int width, int height) const; + // Draws a horizontal rule at Y. Returns the offset it should be moving along Y-axis. + virtual int DrawHorizontalRule(int y) const; + // Draws a line of text. Returns the offset it should be moving along Y-axis. + 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; }; #endif // RECOVERY_UI_H diff --git a/tests/Android.mk b/tests/Android.mk index 346873dbe..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 \ @@ -160,6 +161,7 @@ LOCAL_STATIC_LIBRARIES := \ libfec_rs \ libsquashfs_utils \ libcutils \ + libbrotli \ $(tune2fs_static_libraries) testdata_files := $(call find-subdir-files, testdata/*) diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp index 6f5960bbd..bf25aebb0 100644 --- a/tests/component/imgdiff_test.cpp +++ b/tests/component/imgdiff_test.cpp @@ -328,6 +328,39 @@ TEST(ImgdiffTest, image_mode_simple) { verify_patched_image(src, patch, tgt); } +TEST(ImgdiffTest, image_mode_bad_gzip) { + // Modify the uncompressed length in the gzip footer. + const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e', + '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', + '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', + '\xff', '\xff', '\xff' }; + const std::string src(src_data.cbegin(), src_data.cend()); + TemporaryFile src_file; + ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); + + // Modify the uncompressed length in the gzip footer. + const std::vector<char> tgt_data = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z', '\x1f', '\x8b', + '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac', + '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\xff', '\xff', '\xff' + }; + const std::string tgt(tgt_data.cbegin(), tgt_data.cend()); + TemporaryFile tgt_file; + ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); + + TemporaryFile patch_file; + std::vector<const char*> args = { + "imgdiff", src_file.path, tgt_file.path, patch_file.path, + }; + ASSERT_EQ(0, imgdiff(args.size(), args.data())); + + // Verify. + std::string patch; + ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); + verify_patched_image(src, patch, tgt); +} + TEST(ImgdiffTest, image_mode_different_num_chunks) { // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd) + gzipped "test". const std::vector<char> src_data = { 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 35e87fd56..6c341c111 100644 --- a/tests/component/updater_test.cpp +++ b/tests/component/updater_test.cpp @@ -15,10 +15,12 @@ */ #include <stdio.h> +#include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> +#include <algorithm> #include <memory> #include <string> #include <vector> @@ -29,6 +31,7 @@ #include <android-base/strings.h> #include <android-base/test_utils.h> #include <bootloader_message/bootloader_message.h> +#include <brotli/encode.h> #include <bsdiff.h> #include <gtest/gtest.h> #include <ziparchive/zip_archive.h> @@ -224,102 +227,6 @@ TEST_F(UpdaterTest, file_getprop) { expect("", script6.c_str(), kNoCause); } -TEST_F(UpdaterTest, package_extract_dir) { - // package_extract_dir expects 2 arguments. - expect(nullptr, "package_extract_dir()", kArgsParsingFailure); - expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - std::string zip_path = from_testdata_base("ziptest_valid.zip"); - ZipArchiveHandle handle; - ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); - - // Need to set up the ziphandle. - UpdaterInfo updater_info; - updater_info.package_zip = handle; - - // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>"). - TemporaryDir td; - std::string temp_dir(td.path); - std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")"); - expect("t", script.c_str(), kNoCause, &updater_info); - - // Verify. - std::string data; - std::string file_c = temp_dir + "/c.txt"; - ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); - ASSERT_EQ(kCTxtContents, data); - - std::string file_d = temp_dir + "/d.txt"; - ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); - ASSERT_EQ(kDTxtContents, data); - - // Modify the contents in order to retry. It's expected to be overwritten. - ASSERT_TRUE(android::base::WriteStringToFile("random", file_c)); - ASSERT_TRUE(android::base::WriteStringToFile("random", file_d)); - - // Extract again and verify. - expect("t", script.c_str(), kNoCause, &updater_info); - - ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); - ASSERT_EQ(kCTxtContents, data); - ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); - ASSERT_EQ(kDTxtContents, data); - - // Clean up the temp files under td. - ASSERT_EQ(0, unlink(file_c.c_str())); - ASSERT_EQ(0, unlink(file_d.c_str())); - - // Extracting "b/" (with slash) should give the same result. - script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")"; - expect("t", script.c_str(), kNoCause, &updater_info); - - ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); - ASSERT_EQ(kCTxtContents, data); - ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); - ASSERT_EQ(kDTxtContents, data); - - ASSERT_EQ(0, unlink(file_c.c_str())); - ASSERT_EQ(0, unlink(file_d.c_str())); - - // Extracting "" is allowed. The entries will carry the path name. - script = "package_extract_dir(\"\", \"" + temp_dir + "\")"; - expect("t", script.c_str(), kNoCause, &updater_info); - - std::string file_a = temp_dir + "/a.txt"; - ASSERT_TRUE(android::base::ReadFileToString(file_a, &data)); - ASSERT_EQ(kATxtContents, data); - std::string file_b = temp_dir + "/b.txt"; - ASSERT_TRUE(android::base::ReadFileToString(file_b, &data)); - ASSERT_EQ(kBTxtContents, data); - std::string file_b_c = temp_dir + "/b/c.txt"; - ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data)); - ASSERT_EQ(kCTxtContents, data); - std::string file_b_d = temp_dir + "/b/d.txt"; - ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data)); - ASSERT_EQ(kDTxtContents, data); - - ASSERT_EQ(0, unlink(file_a.c_str())); - ASSERT_EQ(0, unlink(file_b.c_str())); - ASSERT_EQ(0, unlink(file_b_c.c_str())); - ASSERT_EQ(0, unlink(file_b_d.c_str())); - ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str())); - - // Extracting non-existent entry should still give "t". - script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")"; - expect("t", script.c_str(), kNoCause, &updater_info); - - // Only relative zip_path is allowed. - script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")"; - expect("", script.c_str(), kNoCause, &updater_info); - - // Only absolute dest_path is allowed. - script = "package_extract_dir(\"b\", \"path\")"; - expect("", script.c_str(), kNoCause, &updater_info); - - CloseArchive(handle); -} - // TODO: Test extracting to block device. TEST_F(UpdaterTest, package_extract_file) { // package_extract_file expects 1 or 2 arguments. @@ -578,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; @@ -654,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; @@ -672,4 +579,79 @@ TEST_F(UpdaterTest, new_data_short_write) { std::string script_exact_data = "block_image_update(\"" + std::string(update_file.path) + R"(", package_extract_file("transfer_list"), "exact_new_data", "patch_data"))"; expect("t", script_exact_data.c_str(), kNoCause, &updater_info); + CloseArchive(handle); +} + +TEST_F(UpdaterTest, brotli_new_data) { + // Create a zip file with new_data. + TemporaryFile zip_file; + FILE* zip_file_ptr = fdopen(zip_file.fd, "wb"); + ZipWriter zip_writer(zip_file_ptr); + + // Add a brotli compressed new data entry. + ASSERT_EQ(0, zip_writer.StartEntry("new.dat.br", 0)); + + auto generator = []() { return rand() % 128; }; + // Generate 100 blocks of random data. + std::string brotli_new_data; + 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); + ASSERT_TRUE(BrotliEncoderCompress( + BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(), + reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size, encoded_data.data())); + + ASSERT_EQ(0, zip_writer.WriteBytes(encoded_data.data(), encoded_size)); + ASSERT_EQ(0, zip_writer.FinishEntry()); + // Add a dummy patch data. + ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0)); + ASSERT_EQ(0, zip_writer.FinishEntry()); + + // 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", + "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'); + ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size())); + ASSERT_EQ(0, zip_writer.FinishEntry()); + ASSERT_EQ(0, zip_writer.Finish()); + ASSERT_EQ(0, fclose(zip_file_ptr)); + + MemMapping map; + ASSERT_TRUE(map.MapFile(zip_file.path)); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle)); + + // Set up the handler, command_pipe, patch offset & length. + UpdaterInfo updater_info; + updater_info.package_zip = handle; + TemporaryFile temp_pipe; + updater_info.cmd_pipe = fopen(temp_pipe.path, "wb"); + updater_info.package_zip_addr = map.addr; + updater_info.package_zip_len = map.length; + + // Check if we can decompress the new data correctly. + TemporaryFile update_file; + std::string script_new_data = + "block_image_update(\"" + std::string(update_file.path) + + R"(", package_extract_file("transfer_list"), "new.dat.br", "patch_data"))"; + expect("t", script_new_data.c_str(), kNoCause, &updater_info); + + std::string updated_content; + ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content)); + ASSERT_EQ(brotli_new_data, updated_content); + CloseArchive(handle); } 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, ×tamp, 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..3993cb9ea 100644 --- a/tests/unit/rangeset_test.cpp +++ b/tests/unit/rangeset_test.cpp @@ -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 @@ -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); @@ -71,23 +77,25 @@ RecoveryUI::RecoveryUI() } void RecoveryUI::OnKeyDetected(int key_code) { - if (key_code == KEY_POWER) { - has_power_key = true; - } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) { - has_down_key = true; - } else if (key_code == KEY_UP || key_code == KEY_VOLUMEUP) { - has_up_key = true; - } + if (key_code == KEY_POWER) { + has_power_key = true; + } else if (key_code == KEY_DOWN || key_code == KEY_VOLUMEDOWN) { + 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; + } } // Reads input events, handles special hot keys, and adds to the key queue. static void* InputThreadLoop(void*) { - while (true) { - if (!ev_wait(-1)) { - ev_dispatch(); - } + while (true) { + if (!ev_wait(-1)) { + ev_dispatch(); } - return nullptr; + } + return nullptr; } bool RecoveryUI::InitScreensaver() { @@ -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,40 +166,157 @@ 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; + 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; + } - if (ev.type == EV_SYN) { - return 0; - } else 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 - // (positive or negative), fake an up/down - // key event. - rel_sum += ev.value; - if (rel_sum > 3) { - ProcessKey(KEY_DOWN, 1); // press down key - ProcessKey(KEY_DOWN, 0); // and release it - rel_sum = 0; - } else if (rel_sum < -3) { - ProcessKey(KEY_UP, 1); // press up key - ProcessKey(KEY_UP, 0); // and release it - rel_sum = 0; - } - } - } else { + 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 + // (positive or negative), fake an up/down + // key event. + rel_sum += ev.value; + if (rel_sum > 3) { + ProcessKey(KEY_DOWN, 1); // press down key + ProcessKey(KEY_DOWN, 0); // and release it + rel_sum = 0; + } else if (rel_sum < -3) { + ProcessKey(KEY_UP, 1); // press up key + ProcessKey(KEY_UP, 0); // and release it rel_sum = 0; + } } + } else { + rel_sum = 0; + } - if (ev.type == EV_KEY && ev.code <= KEY_MAX) { - ProcessKey(ev.code, ev.value); + 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); + } + + return 0; } // Process a key-up or -down event. A key is "registered" when it is @@ -189,82 +332,84 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { // // updown == 1 for key down events; 0 for key up events void RecoveryUI::ProcessKey(int key_code, int updown) { - bool register_key = false; - bool long_press = false; - bool reboot_enabled; + bool register_key = false; + bool long_press = false; + bool reboot_enabled; - pthread_mutex_lock(&key_queue_mutex); - key_pressed[key_code] = updown; - if (updown) { - ++key_down_count; - key_last_down = key_code; - key_long_press = false; - key_timer_t* info = new key_timer_t; - info->ui = this; - info->key_code = key_code; - info->count = key_down_count; - pthread_t thread; - pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info); - pthread_detach(thread); - } else { - if (key_last_down == key_code) { - long_press = key_long_press; - register_key = true; - } - key_last_down = -1; + pthread_mutex_lock(&key_queue_mutex); + key_pressed[key_code] = updown; + if (updown) { + ++key_down_count; + key_last_down = key_code; + key_long_press = false; + key_timer_t* info = new key_timer_t; + info->ui = this; + info->key_code = key_code; + info->count = key_down_count; + pthread_t thread; + pthread_create(&thread, nullptr, &RecoveryUI::time_key_helper, info); + pthread_detach(thread); + } else { + if (key_last_down == key_code) { + long_press = key_long_press; + register_key = true; } - reboot_enabled = enable_reboot; - pthread_mutex_unlock(&key_queue_mutex); + key_last_down = -1; + } + reboot_enabled = enable_reboot; + pthread_mutex_unlock(&key_queue_mutex); - if (register_key) { - switch (CheckKey(key_code, long_press)) { - case RecoveryUI::IGNORE: - break; - - case RecoveryUI::TOGGLE: - ShowText(!IsTextVisible()); - break; - - case RecoveryUI::REBOOT: - if (reboot_enabled) { - reboot("reboot,"); - while (true) { pause(); } - } - break; - - case RecoveryUI::ENQUEUE: - EnqueueKey(key_code); - break; + if (register_key) { + switch (CheckKey(key_code, long_press)) { + case RecoveryUI::IGNORE: + break; + + case RecoveryUI::TOGGLE: + ShowText(!IsTextVisible()); + break; + + case RecoveryUI::REBOOT: + if (reboot_enabled) { + reboot("reboot,"); + while (true) { + pause(); + } } + break; + + case RecoveryUI::ENQUEUE: + EnqueueKey(key_code); + break; } + } } void* RecoveryUI::time_key_helper(void* cookie) { - key_timer_t* info = static_cast<key_timer_t*>(cookie); - info->ui->time_key(info->key_code, info->count); - delete info; - return nullptr; + key_timer_t* info = static_cast<key_timer_t*>(cookie); + info->ui->time_key(info->key_code, info->count); + delete info; + return nullptr; } void RecoveryUI::time_key(int key_code, int count) { - usleep(750000); // 750 ms == "long" - bool long_press = false; - pthread_mutex_lock(&key_queue_mutex); - if (key_last_down == key_code && key_down_count == count) { - long_press = key_long_press = true; - } - pthread_mutex_unlock(&key_queue_mutex); - if (long_press) KeyLongPress(key_code); + usleep(750000); // 750 ms == "long" + bool long_press = false; + pthread_mutex_lock(&key_queue_mutex); + if (key_last_down == key_code && key_down_count == count) { + long_press = key_long_press = true; + } + pthread_mutex_unlock(&key_queue_mutex); + if (long_press) KeyLongPress(key_code); } void RecoveryUI::EnqueueKey(int key_code) { - pthread_mutex_lock(&key_queue_mutex); - const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); - if (key_queue_len < queue_max) { - key_queue[key_queue_len++] = key_code; - pthread_cond_signal(&key_queue_cond); - } - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_lock(&key_queue_mutex); + const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); + if (key_queue_len < queue_max) { + key_queue[key_queue_len++] = key_code; + pthread_cond_signal(&key_queue_cond); + } + pthread_mutex_unlock(&key_queue_mutex); } int RecoveryUI::WaitKey() { @@ -330,98 +475,104 @@ int RecoveryUI::WaitKey() { } bool RecoveryUI::IsUsbConnected() { - int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); - if (fd < 0) { - printf("failed to open /sys/class/android_usb/android0/state: %s\n", - strerror(errno)); - return 0; - } + int fd = open("/sys/class/android_usb/android0/state", O_RDONLY); + if (fd < 0) { + printf("failed to open /sys/class/android_usb/android0/state: %s\n", strerror(errno)); + return 0; + } - char buf; - // USB is connected if android_usb state is CONNECTED or CONFIGURED. - int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C'); - if (close(fd) < 0) { - printf("failed to close /sys/class/android_usb/android0/state: %s\n", - strerror(errno)); - } - return connected; + char buf; + // USB is connected if android_usb state is CONNECTED or CONFIGURED. + int connected = (TEMP_FAILURE_RETRY(read(fd, &buf, 1)) == 1) && (buf == 'C'); + if (close(fd) < 0) { + printf("failed to close /sys/class/android_usb/android0/state: %s\n", strerror(errno)); + } + return connected; } bool RecoveryUI::IsKeyPressed(int key) { - pthread_mutex_lock(&key_queue_mutex); - int pressed = key_pressed[key]; - pthread_mutex_unlock(&key_queue_mutex); - return pressed; + pthread_mutex_lock(&key_queue_mutex); + int pressed = key_pressed[key]; + pthread_mutex_unlock(&key_queue_mutex); + return pressed; } bool RecoveryUI::IsLongPress() { - pthread_mutex_lock(&key_queue_mutex); - bool result = key_long_press; - pthread_mutex_unlock(&key_queue_mutex); - return result; + pthread_mutex_lock(&key_queue_mutex); + bool result = key_long_press; + pthread_mutex_unlock(&key_queue_mutex); + return result; } bool RecoveryUI::HasThreeButtons() { - return has_power_key && has_up_key && has_down_key; + 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; - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_lock(&key_queue_mutex); + key_queue_len = 0; + pthread_mutex_unlock(&key_queue_mutex); } RecoveryUI::KeyAction RecoveryUI::CheckKey(int key, bool is_long_press) { - pthread_mutex_lock(&key_queue_mutex); - key_long_press = false; - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_lock(&key_queue_mutex); + key_long_press = false; + 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)) { - return TOGGLE; - } - } else { - // Otherwise long press of any button toggles to the text display, - // and there's no way to toggle back (but that's pretty useless anyway). - if (is_long_press && !IsTextVisible()) { - return TOGGLE; - } + // If we have power and volume up keys, that chord is the signal to toggle the text display. + if (HasThreeButtons() || (HasPowerKey() && HasTouchScreen() && touch_screen_allowed_)) { + if ((key == KEY_VOLUMEUP || key == KEY_UP) && IsKeyPressed(KEY_POWER)) { + return TOGGLE; + } + } else { + // Otherwise long press of any button toggles to the text display, + // and there's no way to toggle back (but that's pretty useless anyway). + if (is_long_press && !IsTextVisible()) { + return TOGGLE; + } - // Also, for button-limited devices, a long press is translated to KEY_ENTER. - if (is_long_press && IsTextVisible()) { - EnqueueKey(KEY_ENTER); - return IGNORE; - } + // Also, for button-limited devices, a long press is translated to KEY_ENTER. + if (is_long_press && IsTextVisible()) { + EnqueueKey(KEY_ENTER); + return IGNORE; } + } - // Press power seven times in a row to reboot. - if (key == KEY_POWER) { - pthread_mutex_lock(&key_queue_mutex); - bool reboot_enabled = enable_reboot; - pthread_mutex_unlock(&key_queue_mutex); + // Press power seven times in a row to reboot. + if (key == KEY_POWER) { + pthread_mutex_lock(&key_queue_mutex); + bool reboot_enabled = enable_reboot; + pthread_mutex_unlock(&key_queue_mutex); - if (reboot_enabled) { - ++consecutive_power_keys; - if (consecutive_power_keys >= 7) { - return REBOOT; - } - } - } else { - consecutive_power_keys = 0; + if (reboot_enabled) { + ++consecutive_power_keys; + if (consecutive_power_keys >= 7) { + return REBOOT; + } } + } else { + consecutive_power_keys = 0; + } - last_key = key; - return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE; + last_key = key; + return (IsTextVisible() || screensaver_state_ == ScreensaverState::OFF) ? ENQUEUE : IGNORE; } void RecoveryUI::KeyLongPress(int) { } void RecoveryUI::SetEnableReboot(bool enabled) { - pthread_mutex_lock(&key_queue_mutex); - enable_reboot = enabled; - pthread_mutex_unlock(&key_queue_mutex); + pthread_mutex_lock(&key_queue_mutex); + enable_reboot = enabled; + pthread_mutex_unlock(&key_queue_mutex); } void RecoveryUI::SetLocale(const std::string& new_locale) { @@ -25,163 +25,180 @@ // Abstract class for controlling the user interface during recovery. class RecoveryUI { - public: - RecoveryUI(); + public: + RecoveryUI(); - virtual ~RecoveryUI() { } + virtual ~RecoveryUI() {} - // Initialize the object; called before anything else. UI texts will be - // initialized according to the given locale. Returns true on success. - virtual bool Init(const std::string& locale); + // Initializes the object; called before anything else. UI texts will be initialized according to + // the given locale. Returns true on success. + virtual bool Init(const std::string& locale); - // Show a stage indicator. Call immediately after Init(). - virtual void SetStage(int current, int max) = 0; + // Shows a stage indicator. Called immediately after Init(). + virtual void SetStage(int current, int max) = 0; - // Set the overall recovery state ("background image"). - enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR }; - virtual void SetBackground(Icon icon) = 0; - virtual void SetSystemUpdateText(bool security_update) = 0; + // Sets the overall recovery state ("background image"). + enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, ERROR }; + virtual void SetBackground(Icon icon) = 0; + virtual void SetSystemUpdateText(bool security_update) = 0; - // --- progress indicator --- - enum ProgressType { EMPTY, INDETERMINATE, DETERMINATE }; - virtual void SetProgressType(ProgressType determinate) = 0; - - // Show a progress bar and define the scope of the next operation: - // portion - fraction of the progress bar the next operation will use - // seconds - expected time interval (progress bar moves at this minimum rate) - virtual void ShowProgress(float portion, float seconds) = 0; - - // Set progress bar position (0.0 - 1.0 within the scope defined - // by the last call to ShowProgress). - virtual void SetProgress(float fraction) = 0; - - // --- text log --- - - virtual void ShowText(bool visible) = 0; - - virtual bool IsTextVisible() = 0; - - virtual bool WasTextEverVisible() = 0; - - // Write a message to the on-screen log (shown if the user has - // toggled on the text display). Print() will also dump the message - // to stdout / log file, while PrintOnScreenOnly() not. - virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0; - virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0; - - virtual void ShowFile(const char* filename) = 0; + // --- progress indicator --- + enum ProgressType { EMPTY, INDETERMINATE, DETERMINATE }; + virtual void SetProgressType(ProgressType determinate) = 0; - // --- key handling --- + // Shows a progress bar and define the scope of the next operation: + // portion - fraction of the progress bar the next operation will use + // seconds - expected time interval (progress bar moves at this minimum rate) + virtual void ShowProgress(float portion, float seconds) = 0; - // Wait for a key and return it. May return -1 after timeout. - virtual int WaitKey(); + // Sets progress bar position (0.0 - 1.0 within the scope defined by the last call to + // ShowProgress). + virtual void SetProgress(float fraction) = 0; - virtual bool IsKeyPressed(int key); - virtual bool IsLongPress(); + // --- text log --- - // Returns true if you have the volume up/down and power trio typical - // of phones and tablets, false otherwise. - virtual bool HasThreeButtons(); - - // Erase any queued-up keys. - virtual void FlushKeys(); - - // Called on each key press, even while operations are in progress. - // Return value indicates whether an immediate operation should be - // triggered (toggling the display, rebooting the device), or if - // the key should be enqueued for use by the main thread. - enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE }; - virtual KeyAction CheckKey(int key, bool is_long_press); - - // Called when a key is held down long enough to have been a - // long-press (but before the key is released). This means that - // if the key is eventually registered (released without any other - // keys being pressed in the meantime), CheckKey will be called with - // 'is_long_press' true. - virtual void KeyLongPress(int key); - - // Normally in recovery there's a key sequence that triggers - // immediate reboot of the device, regardless of what recovery is - // doing (with the default CheckKey implementation, it's pressing - // the power button 7 times in row). Call this to enable or - // disable that feature. It is enabled by default. - virtual void SetEnableReboot(bool enabled); - - // --- menu display --- - - // Display some header text followed by a menu of items, which appears - // at the top of the screen (in place of any scrolling ui_print() - // output, if necessary). - virtual void StartMenu(const char* const * headers, const char* const * items, - int initial_selection) = 0; - - // Set the menu highlight to the given index, wrapping if necessary. - // Returns the actual item selected. - virtual int SelectMenu(int sel) = 0; - - // End menu mode, resetting the text overlay so that ui_print() - // statements will be displayed. - virtual void EndMenu() = 0; - - protected: - void EnqueueKey(int key_code); - - // The locale that's used to show the rendered texts. - std::string locale_; - bool rtl_locale_; - - // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% - // of the max_brightness). Because the absolute values may vary across devices. These two - // values can be configured via subclassing. Setting brightness_normal_ to 0 to disable - // screensaver. - unsigned int brightness_normal_; - unsigned int brightness_dimmed_; - - private: - // Key event input queue - pthread_mutex_t key_queue_mutex; - pthread_cond_t key_queue_cond; - int key_queue[256], key_queue_len; - char key_pressed[KEY_MAX + 1]; // under key_queue_mutex - int key_last_down; // under key_queue_mutex - bool key_long_press; // under key_queue_mutex - int key_down_count; // under key_queue_mutex - bool enable_reboot; // under key_queue_mutex - int rel_sum; - - int consecutive_power_keys; - int last_key; - - bool has_power_key; - bool has_up_key; - bool has_down_key; - - struct key_timer_t { - RecoveryUI* ui; - int key_code; - int count; - }; - - pthread_t input_thread_; - - void OnKeyDetected(int key_code); - int OnInputEvent(int fd, uint32_t epevents); - void ProcessKey(int key_code, int updown); - - bool IsUsbConnected(); - - static void* time_key_helper(void* cookie); - void time_key(int key_code, int count); - - void SetLocale(const std::string&); - - enum class ScreensaverState { DISABLED, NORMAL, DIMMED, OFF }; - ScreensaverState screensaver_state_; - // The following two contain the absolute values computed from brightness_normal_ and - // brightness_dimmed_ respectively. - unsigned int brightness_normal_value_; - unsigned int brightness_dimmed_value_; - bool InitScreensaver(); + virtual void ShowText(bool visible) = 0; + + virtual bool IsTextVisible() = 0; + + virtual bool WasTextEverVisible() = 0; + + // Writes a message to the on-screen log (shown if the user has toggled on the text display). + // Print() will also dump the message to stdout / log file, while PrintOnScreenOnly() not. + virtual void Print(const char* fmt, ...) __printflike(2, 3) = 0; + virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0; + + virtual void ShowFile(const char* filename) = 0; + + // --- key handling --- + + // Waits for a key and return it. May return -1 after timeout. + virtual int WaitKey(); + + virtual bool IsKeyPressed(int key); + virtual bool IsLongPress(); + + // Returns true if you have the volume up/down and power trio typical of phones and tablets, false + // 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(); + + // Called on each key press, even while operations are in progress. Return value indicates whether + // an immediate operation should be triggered (toggling the display, rebooting the device), or if + // the key should be enqueued for use by the main thread. + enum KeyAction { ENQUEUE, TOGGLE, REBOOT, IGNORE }; + virtual KeyAction CheckKey(int key, bool is_long_press); + + // Called when a key is held down long enough to have been a long-press (but before the key is + // released). This means that if the key is eventually registered (released without any other keys + // being pressed in the meantime), CheckKey will be called with 'is_long_press' true. + virtual void KeyLongPress(int key); + + // Normally in recovery there's a key sequence that triggers immediate reboot of the device, + // regardless of what recovery is doing (with the default CheckKey implementation, it's pressing + // the power button 7 times in row). Call this to enable or disable that feature. It is enabled by + // default. + virtual void SetEnableReboot(bool enabled); + + // --- menu display --- + + // Display some header text followed by a menu of items, which appears at the top of the screen + // (in place of any scrolling ui_print() output, if necessary). + virtual void StartMenu(const char* const* headers, const char* const* items, + int initial_selection) = 0; + + // Sets the menu highlight to the given index, wrapping if necessary. Returns the actual item + // selected. + virtual int SelectMenu(int sel) = 0; + + // Ends menu mode, resetting the text overlay so that ui_print() statements will be displayed. + virtual void EndMenu() = 0; + + protected: + void EnqueueKey(int key_code); + + // The locale that's used to show the rendered texts. + std::string locale_; + bool rtl_locale_; + + // The normal and dimmed brightness percentages (default: 50 and 25, which means 50% and 25% of + // the max_brightness). Because the absolute values may vary across devices. These two values can + // be configured via subclassing. Setting brightness_normal_ to 0 to disable screensaver. + 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; + int key_queue[256], key_queue_len; + char key_pressed[KEY_MAX + 1]; // under key_queue_mutex + int key_last_down; // under key_queue_mutex + bool key_long_press; // under key_queue_mutex + int key_down_count; // under key_queue_mutex + bool enable_reboot; // under key_queue_mutex + int rel_sum; + + int consecutive_power_keys; + int last_key; + + 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; + int key_code; + int count; + }; + + 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); + + bool IsUsbConnected(); + + static void* time_key_helper(void* cookie); + void time_key(int key_code, int count); + + void SetLocale(const std::string&); + + enum class ScreensaverState { DISABLED, NORMAL, DIMMED, OFF }; + ScreensaverState screensaver_state_; + // The following two contain the absolute values computed from brightness_normal_ and + // brightness_dimmed_ respectively. + unsigned int brightness_normal_value_; + unsigned int brightness_dimmed_value_; + bool InitScreensaver(); }; #endif // RECOVERY_UI_H 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 ad3bdce7a..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 diff --git a/update_verifier/update_verifier.cpp b/update_verifier/update_verifier.cpp index 48242a5d0..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; - } - - 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; - } + 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; - 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() { diff --git a/updater/Android.mk b/updater/Android.mk index a113fe86c..86dc48e30 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -47,6 +47,7 @@ updater_common_static_libraries := \ libcrypto_utils \ libcutils \ libtune2fs \ + libbrotli \ $(tune2fs_static_libraries) # libupdater (static library) diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index df366b0b8..a0b9ad233 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -44,6 +44,7 @@ #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <applypatch/applypatch.h> +#include <brotli/decode.h> #include <openssl/sha.h> #include <private/android_filesystem_config.h> #include <ziparchive/zip_archive.h> @@ -149,7 +150,11 @@ static void allocate(size_t size, std::vector<uint8_t>& buffer) { class RangeSinkWriter { public: RangeSinkWriter(int fd, const RangeSet& tgt) - : fd_(fd), tgt_(tgt), next_range_(0), current_range_left_(0), bytes_written_(0) { + : fd_(fd), + tgt_(tgt), + next_range_(0), + current_range_left_(0), + bytes_written_(0) { CHECK_NE(tgt.size(), static_cast<size_t>(0)); }; @@ -157,6 +162,11 @@ class RangeSinkWriter { return next_range_ == tgt_.size() && current_range_left_ == 0; } + 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"; @@ -166,23 +176,8 @@ class RangeSinkWriter { size_t written = 0; while (size > 0) { // Move to the next range as needed. - if (current_range_left_ == 0) { - if (next_range_ < tgt_.size()) { - const Range& range = tgt_[next_range_]; - off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE; - current_range_left_ = (range.second - range.first) * BLOCKSIZE; - next_range_++; - if (!discard_blocks(fd_, offset, current_range_left_)) { - break; - } - - if (!check_lseek(fd_, offset, SEEK_SET)) { - break; - } - } else { - // We can't write any more; return how many bytes have been written so far. - break; - } + if (!SeekToOutputRange()) { + break; } size_t write_now = size; @@ -210,9 +205,35 @@ class RangeSinkWriter { } private: - // The input data. + // Set up the output cursor, move to next range if needed. + bool SeekToOutputRange() { + // We haven't finished the current range yet. + if (current_range_left_ != 0) { + return true; + } + // We can't write any more; let the write function return how many bytes have been written + // so far. + if (next_range_ >= tgt_.size()) { + return false; + } + + const Range& range = tgt_[next_range_]; + off64_t offset = static_cast<off64_t>(range.first) * BLOCKSIZE; + current_range_left_ = (range.second - range.first) * BLOCKSIZE; + next_range_++; + + if (!discard_blocks(fd_, offset, current_range_left_)) { + return false; + } + if (!check_lseek(fd_, offset, SEEK_SET)) { + return false; + } + return true; + } + + // The output file descriptor. int fd_; - // The destination for the data. + // The destination ranges for the data. const RangeSet& tgt_; // The next range that we should write to. size_t next_range_; @@ -243,8 +264,10 @@ class RangeSinkWriter { struct NewThreadInfo { ZipArchiveHandle za; ZipEntry entry; + bool brotli_compressed; - RangeSinkWriter* writer; + std::unique_ptr<RangeSinkWriter> writer; + BrotliDecoderState* brotli_decoder_state; bool receiver_available; pthread_mutex_t mu; @@ -264,9 +287,14 @@ 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 written = nti->writer->Write(data, size); - data += written; - size -= written; + 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. @@ -281,10 +309,72 @@ static bool receive_new_data(const uint8_t* data, size_t size, void* cookie) { return true; } -static void* unzip_new_data(void* cookie) { +static bool receive_brotli_new_data(const uint8_t* data, size_t size, void* cookie) { NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie); - ProcessZipEntryContents(nti->za, &nti->entry, receive_new_data, nti); + 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); + + if (result == BROTLI_DECODER_RESULT_ERROR) { + LOG(ERROR) << "Decompression failed with " + << BrotliDecoderErrorString(BrotliDecoderGetErrorCode(nti->brotli_decoder_state)); + return false; + } + + 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. + + pthread_mutex_lock(&nti->mu); + nti->writer = nullptr; + pthread_cond_broadcast(&nti->cv); + pthread_mutex_unlock(&nti->mu); + } + } + + return true; +} + +static void* unzip_new_data(void* cookie) { + NewThreadInfo* nti = static_cast<NewThreadInfo*>(cookie); + 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) { @@ -1142,9 +1232,8 @@ static int PerformCommandNew(CommandParameters& params) { if (params.canwrite) { LOG(INFO) << " writing " << tgt.blocks() << " blocks of new data"; - RangeSinkWriter writer(params.fd, tgt); pthread_mutex_lock(¶ms.nti.mu); - params.nti.writer = &writer; + params.nti.writer = std::make_unique<RangeSinkWriter>(params.fd, tgt); pthread_cond_broadcast(¶ms.nti.cv); while (params.nti.writer != nullptr) { @@ -1384,6 +1473,11 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, if (params.canwrite) { params.nti.za = za; params.nti.entry = new_entry; + params.nti.brotli_compressed = android::base::EndsWith(new_data_fn->data, ".br"); + if (params.nti.brotli_compressed) { + // Initialize brotli decoder state. + params.nti.brotli_decoder_state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr); + } params.nti.receiver_available = true; pthread_mutex_init(¶ms.nti.mu, nullptr); @@ -1526,6 +1620,10 @@ pbiudone: } // params.fd will be automatically closed because it's a unique_fd. + if (params.nti.brotli_decoder_state != nullptr) { + BrotliDecoderDestroyInstance(params.nti.brotli_decoder_state); + } + // Only delete the stash if the update cannot be resumed, or it's a verification run and we // created the stash. if (params.isunresumable || (!params.canwrite && params.createdstash)) { diff --git a/updater/include/updater/rangeset.h b/updater/include/updater/rangeset.h index fad038043..b67c98724 100644 --- a/updater/include/updater/rangeset.h +++ b/updater/include/updater/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/updater/install.cpp b/updater/install.cpp index c9a3a0799..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) @@ -322,8 +294,7 @@ Value* FormatFn(const char* name, State* state, const std::vector<std::unique_pt return StringValue(location); } - const char* e2fsdroid_argv[] = { "/sbin/e2fsdroid_static", "-e", "-S", - "/file_contexts", "-a", mount_point.c_str(), + 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) { 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; } @@ -27,9 +27,9 @@ bool VrRecoveryUI::InitTextParams() { return true; } -void VrRecoveryUI::DrawTextLine(int x, int* y, const char* line, bool bold) const { +int VrRecoveryUI::DrawTextLine(int x, int y, const char* line, bool bold) const { int mid_divide = gr_fb_width() / 2; - gr_text(gr_sys_font(), x + kStereoOffset, *y, line, bold); - gr_text(gr_sys_font(), x - kStereoOffset + mid_divide, *y, line, bold); - *y += char_height_ + 4; + gr_text(gr_sys_font(), x + kStereoOffset, y, line, bold); + gr_text(gr_sys_font(), x - kStereoOffset + mid_divide, y, line, bold); + return char_height_ + 4; } @@ -20,17 +20,17 @@ #include "screen_ui.h" class VrRecoveryUI : public ScreenRecoveryUI { - public: - VrRecoveryUI(); + public: + VrRecoveryUI(); - protected: - // Pixel offsets to move drawing functions to visible range. - // Can vary per device depending on screen size and lens distortion. - const int kStereoOffset; + protected: + // Pixel offsets to move drawing functions to visible range. + // Can vary per device depending on screen size and lens distortion. + const int kStereoOffset; - bool InitTextParams() override; + bool InitTextParams() override; - void DrawTextLine(int x, int* y, const char* line, bool bold) const override; + int DrawTextLine(int x, int y, const char* line, bool bold) const override; }; #endif // RECOVERY_VR_UI_H diff --git a/wear_ui.cpp b/wear_ui.cpp index 6c0286558..18c30d34a 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -45,167 +45,158 @@ static WearRecoveryUI* self = NULL; // Return the current time as a double (including fractions of a second). static double now() { - struct timeval tv; - gettimeofday(&tv, NULL); - return tv.tv_sec + tv.tv_usec / 1000000.0; + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; } -WearRecoveryUI::WearRecoveryUI() : - progress_bar_y(259), - outer_height(0), - outer_width(0), - menu_unusable_rows(0) { - intro_frames = 22; - loop_frames = 60; - animation_fps = 30; +WearRecoveryUI::WearRecoveryUI() + : progress_bar_y(259), outer_height(0), outer_width(0), menu_unusable_rows(0) { + intro_frames = 22; + loop_frames = 60; + animation_fps = 30; - for (size_t i = 0; i < 5; i++) - backgroundIcon[i] = NULL; + for (size_t i = 0; i < 5; i++) backgroundIcon[i] = NULL; - self = this; + self = this; } -int WearRecoveryUI::GetProgressBaseline() { - return progress_bar_y; +int WearRecoveryUI::GetProgressBaseline() const { + return progress_bar_y; } // Draw background frame on the screen. Does not flip pages. // Should only be called with updateMutex locked. // TODO merge drawing routines with screen_ui -void WearRecoveryUI::draw_background_locked() -{ - pagesIdentical = false; - gr_color(0, 0, 0, 255); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - - if (currentIcon != NONE) { - GRSurface* surface; - if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { - if (!intro_done) { - surface = introFrames[current_frame]; - } else { - surface = loopFrames[current_frame]; - } - } - else { - surface = backgroundIcon[currentIcon]; - } +void WearRecoveryUI::draw_background_locked() { + pagesIdentical = false; + gr_color(0, 0, 0, 255); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + if (currentIcon != NONE) { + GRSurface* surface; + if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { + if (!intro_done) { + surface = introFrames[current_frame]; + } else { + surface = loopFrames[current_frame]; + } + } else { + surface = backgroundIcon[currentIcon]; + } - int width = gr_get_width(surface); - int height = gr_get_height(surface); + int width = gr_get_width(surface); + int height = gr_get_height(surface); - int x = (gr_fb_width() - width) / 2; - int y = (gr_fb_height() - height) / 2; + int x = (gr_fb_width() - width) / 2; + int y = (gr_fb_height() - height) / 2; - gr_blit(surface, 0, 0, width, height, x, y); - } + gr_blit(surface, 0, 0, width, height, x, y); + } } -static const char* HEADERS[] = { - "Swipe up/down to move.", - "Swipe left/right to select.", - "", - NULL +static const char* SWIPE_HELP[] = { + "Swipe up/down to move.", + "Swipe left/right to select.", + "", + NULL }; // TODO merge drawing routines with screen_ui -void WearRecoveryUI::draw_screen_locked() -{ - char cur_selection_str[50]; +void WearRecoveryUI::draw_screen_locked() { + char cur_selection_str[50]; + + draw_background_locked(); + if (!show_text) { + draw_foreground_locked(); + } else { + SetColor(TEXT_FILL); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - draw_background_locked(); - if (!show_text) { - draw_foreground_locked(); - } else { - SetColor(TEXT_FILL); - gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - - int y = outer_height; - int x = outer_width; - if (show_menu) { - std::string recovery_fingerprint = - android::base::GetProperty("ro.bootimage.build.fingerprint", ""); - SetColor(HEADER); - DrawTextLine(x + 4, &y, "Android Recovery", true); - for (auto& chunk: android::base::Split(recovery_fingerprint, ":")) { - DrawTextLine(x +4, &y, chunk.c_str(), false); - } - - // This is actually the help strings. - DrawTextLines(x + 4, &y, HEADERS); - SetColor(HEADER); - DrawTextLines(x + 4, &y, menu_headers_); - - // Show the current menu item number in relation to total number if - // items don't fit on the screen. - if (menu_items > menu_end - menu_start) { - sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items); - gr_text(gr_sys_font(), x+4, y, cur_selection_str, 1); - y += char_height_+4; - } - - // Menu begins here - SetColor(MENU); - - for (int i = menu_start; i < menu_end; ++i) { - - if (i == menu_sel) { - // draw the highlight bar - SetColor(MENU_SEL_BG); - gr_fill(x, y-2, gr_fb_width()-x, y+char_height_+2); - // white text of selected item - SetColor(MENU_SEL_FG); - if (menu_[i][0]) { - gr_text(gr_sys_font(), x + 4, y, menu_[i], 1); - } - SetColor(MENU); - } else if (menu_[i][0]) { - gr_text(gr_sys_font(), x + 4, y, menu_[i], 0); - } - y += char_height_+4; - } - SetColor(MENU); - y += 4; - gr_fill(0, y, gr_fb_width(), y+2); - y += 4; - } + int y = outer_height; + int x = outer_width; + if (show_menu) { + std::string recovery_fingerprint = + android::base::GetProperty("ro.bootimage.build.fingerprint", ""); + SetColor(HEADER); + y += DrawTextLine(x + 4, y, "Android Recovery", true); + for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) { + y += DrawTextLine(x + 4, y, chunk.c_str(), false); + } - SetColor(LOG); - - // display from the bottom up, until we hit the top of the - // screen, the bottom of the menu, or we've displayed the - // entire text buffer. - 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_; - ty -= char_height_, ++count) { - gr_text(gr_sys_font(), x+4, ty, text_[row], 0); - --row; - if (row < 0) row = text_rows_ - 1; + // This is actually the help strings. + y += DrawTextLines(x + 4, y, SWIPE_HELP); + SetColor(HEADER); + y += DrawTextLines(x + 4, y, menu_headers_); + + // Show the current menu item number in relation to total number if + // items don't fit on the screen. + if (menu_items > menu_end - menu_start) { + sprintf(cur_selection_str, "Current item: %d/%d", menu_sel + 1, menu_items); + gr_text(gr_sys_font(), x + 4, y, cur_selection_str, 1); + y += char_height_ + 4; + } + + // Menu begins here + SetColor(MENU); + + for (int i = menu_start; i < menu_end; ++i) { + if (i == menu_sel) { + // draw the highlight bar + SetColor(MENU_SEL_BG); + gr_fill(x, y - 2, gr_fb_width() - x, y + char_height_ + 2); + // white text of selected item + SetColor(MENU_SEL_FG); + if (menu_[i][0]) { + gr_text(gr_sys_font(), x + 4, y, menu_[i], 1); + } + SetColor(MENU); + } else if (menu_[i][0]) { + gr_text(gr_sys_font(), x + 4, y, menu_[i], 0); } + y += char_height_ + 4; + } + SetColor(MENU); + y += 4; + gr_fill(0, y, gr_fb_width(), y + 2); + y += 4; + } + + SetColor(LOG); + + // display from the bottom up, until we hit the top of the + // screen, the bottom of the menu, or we've displayed the + // entire text buffer. + 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_; + ty -= char_height_, ++count) { + gr_text(gr_sys_font(), x + 4, ty, text_[row], 0); + --row; + if (row < 0) row = text_rows_ - 1; } + } } // TODO merge drawing routines with screen_ui void WearRecoveryUI::update_progress_locked() { - draw_screen_locked(); - gr_flip(); + draw_screen_locked(); + gr_flip(); } bool WearRecoveryUI::InitTextParams() { - if (!ScreenRecoveryUI::InitTextParams()) { - return false; - } + if (!ScreenRecoveryUI::InitTextParams()) { + return false; + } - text_cols_ = (gr_fb_width() - (outer_width * 2)) / char_width_; + text_cols_ = (gr_fb_width() - (outer_width * 2)) / char_width_; - if (text_rows_ > kMaxRows) text_rows_ = kMaxRows; - if (text_cols_ > kMaxCols) text_cols_ = kMaxCols; + 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; + visible_text_rows = (gr_fb_height() - (outer_height * 2)) / char_height_; + return true; } bool WearRecoveryUI::Init(const std::string& locale) { @@ -222,7 +213,8 @@ bool WearRecoveryUI::Init(const std::string& locale) { return true; } -void WearRecoveryUI::SetStage(int current, int max) {} +void WearRecoveryUI::SetStage(int current, int max) { +} void WearRecoveryUI::Print(const char* fmt, ...) { char buf[256]; @@ -252,165 +244,153 @@ void WearRecoveryUI::Print(const char* fmt, ...) { pthread_mutex_unlock(&updateMutex); } -void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * items, +void WearRecoveryUI::StartMenu(const char* const* headers, const char* const* items, int initial_selection) { - pthread_mutex_lock(&updateMutex); - if (text_rows_ > 0 && text_cols_ > 0) { - menu_headers_ = headers; - size_t i = 0; - // "i < text_rows_" is removed from the loop termination condition, - // which is different from the one in ScreenRecoveryUI::StartMenu(). - // Because WearRecoveryUI supports scrollable menu, it's fine to have - // more entries than text_rows_. The menu may be truncated otherwise. - // Bug: 23752519 - for (; items[i] != nullptr; i++) { - strncpy(menu_[i], items[i], text_cols_ - 1); - menu_[i][text_cols_ - 1] = '\0'; - } - menu_items = i; - show_menu = true; - menu_sel = initial_selection; - menu_start = 0; - menu_end = visible_text_rows - 1 - menu_unusable_rows; - if (menu_items <= menu_end) - menu_end = menu_items; - update_screen_locked(); + pthread_mutex_lock(&updateMutex); + if (text_rows_ > 0 && text_cols_ > 0) { + menu_headers_ = headers; + size_t i = 0; + // "i < text_rows_" is removed from the loop termination condition, + // which is different from the one in ScreenRecoveryUI::StartMenu(). + // Because WearRecoveryUI supports scrollable menu, it's fine to have + // more entries than text_rows_. The menu may be truncated otherwise. + // Bug: 23752519 + for (; items[i] != nullptr; i++) { + strncpy(menu_[i], items[i], text_cols_ - 1); + menu_[i][text_cols_ - 1] = '\0'; } - pthread_mutex_unlock(&updateMutex); + menu_items = i; + show_menu = true; + menu_sel = initial_selection; + menu_start = 0; + menu_end = visible_text_rows - 1 - menu_unusable_rows; + if (menu_items <= menu_end) menu_end = menu_items; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); } int WearRecoveryUI::SelectMenu(int sel) { - int old_sel; - pthread_mutex_lock(&updateMutex); - if (show_menu) { - old_sel = menu_sel; - menu_sel = sel; - if (menu_sel < 0) menu_sel = 0; - if (menu_sel >= menu_items) menu_sel = menu_items-1; - if (menu_sel < menu_start) { - menu_start--; - menu_end--; - } else if (menu_sel >= menu_end && menu_sel < menu_items) { - menu_end++; - menu_start++; - } - sel = menu_sel; - if (menu_sel != old_sel) update_screen_locked(); + int old_sel; + pthread_mutex_lock(&updateMutex); + if (show_menu) { + old_sel = menu_sel; + menu_sel = sel; + if (menu_sel < 0) menu_sel = 0; + if (menu_sel >= menu_items) menu_sel = menu_items - 1; + if (menu_sel < menu_start) { + menu_start--; + menu_end--; + } else if (menu_sel >= menu_end && menu_sel < menu_items) { + menu_end++; + menu_start++; } - pthread_mutex_unlock(&updateMutex); - return sel; + sel = menu_sel; + if (menu_sel != old_sel) update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); + return sel; } void WearRecoveryUI::ShowFile(FILE* fp) { - std::vector<off_t> offsets; - offsets.push_back(ftello(fp)); - ClearText(); - - struct stat sb; - fstat(fileno(fp), &sb); - - bool show_prompt = false; - while (true) { - if (show_prompt) { - Print("--(%d%% of %d bytes)--", - static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))), - static_cast<int>(sb.st_size)); - Redraw(); - while (show_prompt) { - show_prompt = false; - int key = WaitKey(); - if (key == KEY_POWER || key == KEY_ENTER) { - return; - } else if (key == KEY_UP || key == KEY_VOLUMEUP) { - if (offsets.size() <= 1) { - show_prompt = true; - } else { - offsets.pop_back(); - fseek(fp, offsets.back(), SEEK_SET); - } - } else { - if (feof(fp)) { - return; - } - offsets.push_back(ftello(fp)); - } - } - ClearText(); - } - - int ch = getc(fp); - if (ch == EOF) { - text_row_ = text_top_ = text_rows_ - 2; + std::vector<off_t> offsets; + offsets.push_back(ftello(fp)); + ClearText(); + + struct stat sb; + fstat(fileno(fp), &sb); + + bool show_prompt = false; + while (true) { + if (show_prompt) { + Print("--(%d%% of %d bytes)--", + static_cast<int>(100 * (double(ftello(fp)) / double(sb.st_size))), + static_cast<int>(sb.st_size)); + Redraw(); + while (show_prompt) { + show_prompt = false; + int key = WaitKey(); + if (key == KEY_POWER || key == KEY_ENTER) { + return; + } else if (key == KEY_UP || key == KEY_VOLUMEUP) { + if (offsets.size() <= 1) { show_prompt = true; + } else { + offsets.pop_back(); + fseek(fp, offsets.back(), SEEK_SET); + } } else { - PutChar(ch); - if (text_col_ == 0 && text_row_ >= text_rows_ - 2) { - text_top_ = text_row_; - show_prompt = true; - } + if (feof(fp)) { + return; + } + offsets.push_back(ftello(fp)); } + } + ClearText(); } -} -void WearRecoveryUI::PutChar(char ch) { - pthread_mutex_lock(&updateMutex); - if (ch != '\n') text_[text_row_][text_col_++] = ch; - if (ch == '\n' || text_col_ >= text_cols_) { - text_col_ = 0; - ++text_row_; + int ch = getc(fp); + if (ch == EOF) { + text_row_ = text_top_ = text_rows_ - 2; + show_prompt = true; + } else { + PutChar(ch); + if (text_col_ == 0 && text_row_ >= text_rows_ - 2) { + text_top_ = text_row_; + show_prompt = true; + } } - pthread_mutex_unlock(&updateMutex); + } } -void WearRecoveryUI::ShowFile(const char* filename) { - FILE* fp = fopen_path(filename, "re"); - if (fp == nullptr) { - Print(" Unable to open %s: %s\n", filename, strerror(errno)); - return; - } - ShowFile(fp); - fclose(fp); +void WearRecoveryUI::PutChar(char ch) { + pthread_mutex_lock(&updateMutex); + if (ch != '\n') text_[text_row_][text_col_++] = ch; + if (ch == '\n' || text_col_ >= text_cols_) { + text_col_ = 0; + ++text_row_; + } + pthread_mutex_unlock(&updateMutex); } -void WearRecoveryUI::ClearText() { - pthread_mutex_lock(&updateMutex); - text_col_ = 0; - text_row_ = 0; - text_top_ = 1; - for (size_t i = 0; i < text_rows_; ++i) { - memset(text_[i], 0, text_cols_ + 1); - } - pthread_mutex_unlock(&updateMutex); +void WearRecoveryUI::ShowFile(const char* filename) { + FILE* fp = fopen_path(filename, "re"); + if (fp == nullptr) { + Print(" Unable to open %s: %s\n", filename, strerror(errno)); + return; + } + ShowFile(fp); + fclose(fp); } void WearRecoveryUI::PrintOnScreenOnly(const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - PrintV(fmt, false, ap); - va_end(ap); + va_list ap; + va_start(ap, fmt); + PrintV(fmt, false, ap); + va_end(ap); } void WearRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) { - std::string str; - android::base::StringAppendV(&str, fmt, ap); + std::string str; + android::base::StringAppendV(&str, fmt, ap); - if (copy_to_stdout) { - fputs(str.c_str(), stdout); - } + if (copy_to_stdout) { + fputs(str.c_str(), stdout); + } - pthread_mutex_lock(&updateMutex); - if (text_rows_ > 0 && text_cols_ > 0) { - for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { - if (*ptr == '\n' || text_col_ >= text_cols_) { - text_[text_row_][text_col_] = '\0'; - text_col_ = 0; - text_row_ = (text_row_ + 1) % text_rows_; - if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; - } - if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; - } + pthread_mutex_lock(&updateMutex); + if (text_rows_ > 0 && text_cols_ > 0) { + for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { + if (*ptr == '\n' || text_col_ >= text_cols_) { text_[text_row_][text_col_] = '\0'; - update_screen_locked(); + text_col_ = 0; + text_row_ = (text_row_ + 1) % text_rows_; + if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; + } + if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; } - pthread_mutex_unlock(&updateMutex); + text_[text_row_][text_col_] = '\0'; + update_screen_locked(); + } + pthread_mutex_unlock(&updateMutex); } @@ -22,64 +22,61 @@ #include <string> class WearRecoveryUI : public ScreenRecoveryUI { - public: - WearRecoveryUI(); + public: + WearRecoveryUI(); - bool Init(const std::string& locale) override; + bool Init(const std::string& locale) override; - void SetStage(int current, int max) override; + void SetStage(int current, int max) override; - // printing messages - void Print(const char* fmt, ...) override; - void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3); - void ShowFile(const char* filename) override; - void ShowFile(FILE* fp) override; + // printing messages + void Print(const char* fmt, ...) override; + void PrintOnScreenOnly(const char* fmt, ...) override __printflike(2, 3); + void ShowFile(const char* filename) override; + void ShowFile(FILE* fp) override; - // menu display - void StartMenu(const char* const * headers, const char* const * items, - int initial_selection) override; - int SelectMenu(int sel) override; + // menu display + void StartMenu(const char* const* headers, const char* const* items, + int initial_selection) override; + int SelectMenu(int sel) override; - protected: - // progress bar vertical position, it's centered horizontally - int progress_bar_y; + protected: + // progress bar vertical position, it's centered horizontally + int progress_bar_y; - // outer of window - int outer_height, outer_width; + // outer of window + int outer_height, outer_width; - // 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; + // 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; - int GetProgressBaseline() override; + int GetProgressBaseline() const override; - bool InitTextParams() override; + bool InitTextParams() override; - void update_progress_locked() override; + void update_progress_locked() override; - void PrintV(const char*, bool, va_list) override; + void PrintV(const char*, bool, va_list) override; - private: - GRSurface* backgroundIcon[5]; + private: + GRSurface* backgroundIcon[5]; - static const int kMaxCols = 96; - static const int kMaxRows = 96; + static const int kMaxCols = 96; + static const int kMaxRows = 96; - // Number of text rows seen on screen - int visible_text_rows; + // Number of text rows seen on screen + int visible_text_rows; - const char* const* menu_headers_; - int menu_start, menu_end; + const char* const* menu_headers_; + int menu_start, menu_end; - pthread_t progress_t; + pthread_t progress_t; - void draw_background_locked() override; - void draw_screen_locked() override; - void draw_progress_locked(); + void draw_background_locked() override; + void draw_screen_locked() override; - void PutChar(char); - void ClearText(); + void PutChar(char); }; #endif // RECOVERY_WEAR_UI_H |