diff --git a/Android.bp b/Android.bp index 9b2f80f0..67ece850 100644 --- a/Android.bp +++ b/Android.bp @@ -35,7 +35,10 @@ package { // See: http://go/android-license-faq license { name: "bootable_recovery_license", - visibility: [":__subpackages__"], + visibility: [ + ":__subpackages__", + "//bootable/deprecated-ota:__subpackages__", + ], license_kinds: [ "SPDX-license-identifier-Apache-2.0", "SPDX-license-identifier-MIT", diff --git a/Android.mk b/Android.mk index 8506040f..3d1ee39c 100644 --- a/Android.mk +++ b/Android.mk @@ -84,5 +84,3 @@ endif include $(BUILD_PHONY_PACKAGE) -include \ - $(LOCAL_PATH)/updater/Android.mk \ diff --git a/applypatch/Android.bp b/applypatch/Android.bp deleted file mode 100644 index 0d6d23b9..00000000 --- a/applypatch/Android.bp +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (C) 2017 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - default_applicable_licenses: ["bootable_recovery_applypatch_license"], -} - -// Added automatically by a large-scale-change -// See: http://go/android-license-faq -license { - name: "bootable_recovery_applypatch_license", - visibility: [":__subpackages__"], - license_kinds: [ - "SPDX-license-identifier-Apache-2.0", - ], - license_text: [ - "NOTICE", - ], -} - -cc_defaults { - name: "applypatch_defaults", - - cflags: [ - "-D_FILE_OFFSET_BITS=64", - "-DZLIB_CONST", - "-Wall", - "-Werror", - ], - - local_include_dirs: [ - "include", - ], -} - -cc_library_static { - name: "libapplypatch", - - host_supported: true, - vendor_available: true, - - defaults: [ - "applypatch_defaults", - ], - - srcs: [ - "applypatch.cpp", - "bspatch.cpp", - "freecache.cpp", - "imgpatch.cpp", - ], - - export_include_dirs: [ - "include", - ], - - static_libs: [ - "libbase", - "libbspatch", - "libbz", - "libedify", - "libotautil", - "libz_stable", - ], - - shared_libs: [ - "libcrypto", - ], - - target: { - darwin: { - enabled: false, - }, - }, -} - -cc_library_static { - name: "libapplypatch_modes", - vendor_available: true, - - defaults: [ - "applypatch_defaults", - ], - - srcs: [ - "applypatch_modes.cpp", - ], - - static_libs: [ - "libapplypatch", - "libbase", - "libedify", - "libotautil", - ], - - shared_libs: [ - "libcrypto", - ], -} - -cc_binary { - name: "applypatch", - vendor: true, - - defaults: [ - "applypatch_defaults", - ], - - srcs: [ - "applypatch_main.cpp", - ], - - static_libs: [ - "libapplypatch_modes", - "libapplypatch", - "libedify", - "libotautil", - - // External dependencies. - "libbspatch", - "libbrotli", - "libbz", - ], - - shared_libs: [ - "libbase", - "libcrypto", - "liblog", - "libz_stable", - "libziparchive", - ], - - init_rc: [ - "vendor_flash_recovery.rc", - ], -} - -cc_library_static { - name: "libimgdiff", - host_supported: true, - defaults: [ - "applypatch_defaults", - ], - - srcs: [ - "imgdiff.cpp", - ], - - export_include_dirs: [ - "include", - ], - - static_libs: [ - "libbase", - "libbsdiff", - "libdivsufsort", - "libdivsufsort64", - "liblog", - "libotautil", - "libutils", - "libz_stable", - "libziparchive", - ], -} - -cc_binary_host { - name: "imgdiff", - srcs: [ - "imgdiff_main.cpp", - ], - - defaults: [ - "applypatch_defaults", - ], - - static_libs: [ - "libimgdiff", - "libotautil", - "libbsdiff", - "libdivsufsort", - "libdivsufsort64", - "libziparchive", - "libbase", - "libutils", - "liblog", - "libbrotli", - "libbz", - "libz_stable", - ], -} diff --git a/applypatch/NOTICE b/applypatch/NOTICE deleted file mode 100644 index 6156a0ca..00000000 --- a/applypatch/NOTICE +++ /dev/null @@ -1,41 +0,0 @@ -Copyright (C) 2009 The Android Open Source Project - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - - -bsdiff.c -bspatch.c - -Copyright 2003-2005 Colin Percival -All rights reserved - -Redistribution and use in source and binary forms, with or without -modification, are permitted providing that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING -IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/applypatch/applypatch.cpp b/applypatch/applypatch.cpp deleted file mode 100644 index adda6976..00000000 --- a/applypatch/applypatch.cpp +++ /dev/null @@ -1,457 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "applypatch/applypatch.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "edify/expr.h" -#include "otautil/paths.h" -#include "otautil/print_sha1.h" - -using namespace std::string_literals; - -static bool GenerateTarget(const Partition& target, const FileContents& source_file, - const Value& patch, const Value* bonus_data, bool backup_source); - -bool LoadFileContents(const std::string& filename, FileContents* file) { - // No longer allow loading contents from eMMC partitions. - if (android::base::StartsWith(filename, "EMMC:")) { - return false; - } - - std::string data; - if (!android::base::ReadFileToString(filename, &data)) { - PLOG(ERROR) << "Failed to read \"" << filename << "\""; - return false; - } - - file->data = std::vector(data.begin(), data.end()); - SHA1(file->data.data(), file->data.size(), file->sha1); - return true; -} - -// Reads the contents of a Partition to the given FileContents buffer. -static bool ReadPartitionToBuffer(const Partition& partition, FileContents* out, - bool check_backup) { - uint8_t expected_sha1[SHA_DIGEST_LENGTH]; - if (ParseSha1(partition.hash, expected_sha1) != 0) { - LOG(ERROR) << "Failed to parse target hash \"" << partition.hash << "\""; - return false; - } - - android::base::unique_fd dev(open(partition.name.c_str(), O_RDONLY)); - if (dev == -1) { - PLOG(ERROR) << "Failed to open eMMC partition \"" << partition << "\""; - } else { - std::vector buffer(partition.size); - if (!android::base::ReadFully(dev, buffer.data(), buffer.size())) { - PLOG(ERROR) << "Failed to read " << buffer.size() << " bytes of data for partition " - << partition; - } else { - SHA1(buffer.data(), buffer.size(), out->sha1); - if (memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) { - out->data = std::move(buffer); - return true; - } - } - } - - if (!check_backup) { - LOG(ERROR) << "Partition contents don't have the expected checksum"; - return false; - } - - if (LoadFileContents(Paths::Get().cache_temp_source(), out) && - memcmp(out->sha1, expected_sha1, SHA_DIGEST_LENGTH) == 0) { - return true; - } - - LOG(ERROR) << "Both of partition contents and backup don't have the expected checksum"; - return false; -} - -bool SaveFileContents(const std::string& filename, const FileContents* file) { - android::base::unique_fd fd( - open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR)); - if (fd == -1) { - PLOG(ERROR) << "Failed to open \"" << filename << "\" for write"; - return false; - } - - if (!android::base::WriteFully(fd, file->data.data(), file->data.size())) { - PLOG(ERROR) << "Failed to write " << file->data.size() << " bytes of data to " << filename; - return false; - } - - if (fsync(fd) != 0) { - PLOG(ERROR) << "Failed to fsync \"" << filename << "\""; - return false; - } - - if (close(fd.release()) != 0) { - PLOG(ERROR) << "Failed to close \"" << filename << "\""; - return false; - } - - return true; -} - -// Writes a memory buffer to 'target' Partition. -static bool WriteBufferToPartition(const FileContents& file_contents, const Partition& partition) { - const unsigned char* data = file_contents.data.data(); - size_t len = file_contents.data.size(); - size_t start = 0; - bool success = false; - for (size_t attempt = 0; attempt < 2; ++attempt) { - android::base::unique_fd fd(open(partition.name.c_str(), O_RDWR)); - if (fd == -1) { - PLOG(ERROR) << "Failed to open \"" << partition << "\""; - return false; - } - - if (TEMP_FAILURE_RETRY(lseek(fd, start, SEEK_SET)) == -1) { - PLOG(ERROR) << "Failed to seek to " << start << " on \"" << partition << "\""; - return false; - } - - if (!android::base::WriteFully(fd, data + start, len - start)) { - PLOG(ERROR) << "Failed to write " << len - start << " bytes to \"" << partition << "\""; - return false; - } - - if (fsync(fd) != 0) { - PLOG(ERROR) << "Failed to sync \"" << partition << "\""; - return false; - } - if (close(fd.release()) != 0) { - PLOG(ERROR) << "Failed to close \"" << partition << "\""; - return false; - } - - fd.reset(open(partition.name.c_str(), O_RDONLY)); - if (fd == -1) { - PLOG(ERROR) << "Failed to reopen \"" << partition << "\" for verification"; - return false; - } - - // Drop caches so our subsequent verification read won't just be reading the cache. - sync(); - std::string drop_cache = "/proc/sys/vm/drop_caches"; - if (!android::base::WriteStringToFile("3\n", drop_cache)) { - PLOG(ERROR) << "Failed to write to " << drop_cache; - } else { - LOG(INFO) << " caches dropped"; - } - sleep(1); - - // Verify. - if (TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_SET)) == -1) { - PLOG(ERROR) << "Failed to seek to 0 on " << partition; - return false; - } - - unsigned char buffer[4096]; - start = len; - for (size_t p = 0; p < len; p += sizeof(buffer)) { - size_t to_read = len - p; - if (to_read > sizeof(buffer)) { - to_read = sizeof(buffer); - } - - if (!android::base::ReadFully(fd, buffer, to_read)) { - PLOG(ERROR) << "Failed to verify-read " << partition << " at " << p; - return false; - } - - if (memcmp(buffer, data + p, to_read) != 0) { - LOG(ERROR) << "Verification failed starting at " << p; - start = p; - break; - } - } - - if (start == len) { - LOG(INFO) << "Verification read succeeded (attempt " << attempt + 1 << ")"; - success = true; - break; - } - - if (close(fd.release()) != 0) { - PLOG(ERROR) << "Failed to close " << partition; - return false; - } - } - - if (!success) { - LOG(ERROR) << "Failed to verify after all attempts"; - return false; - } - - sync(); - - return true; -} - -int ParseSha1(const std::string& str, uint8_t* digest) { - const char* ps = str.c_str(); - uint8_t* pd = digest; - for (int i = 0; i < SHA_DIGEST_LENGTH * 2; ++i, ++ps) { - int digit; - if (*ps >= '0' && *ps <= '9') { - digit = *ps - '0'; - } else if (*ps >= 'a' && *ps <= 'f') { - digit = *ps - 'a' + 10; - } else if (*ps >= 'A' && *ps <= 'F') { - digit = *ps - 'A' + 10; - } else { - return -1; - } - if (i % 2 == 0) { - *pd = digit << 4; - } else { - *pd |= digit; - ++pd; - } - } - if (*ps != '\0') return -1; - return 0; -} - -bool PatchPartitionCheck(const Partition& target, const Partition& source) { - FileContents target_file; - FileContents source_file; - return (ReadPartitionToBuffer(target, &target_file, false) || - ReadPartitionToBuffer(source, &source_file, true)); -} - -int ShowLicenses() { - ShowBSDiffLicense(); - return 0; -} - -bool PatchPartition(const Partition& target, const Partition& source, const Value& patch, - const Value* bonus, bool backup_source) { - LOG(INFO) << "Patching " << target.name; - - // We try to load and check against the target hash first. - FileContents target_file; - if (ReadPartitionToBuffer(target, &target_file, false)) { - // The early-exit case: the patch was already applied, this file has the desired hash, nothing - // for us to do. - LOG(INFO) << " already " << target.hash.substr(0, 8); - return true; - } - - FileContents source_file; - if (ReadPartitionToBuffer(source, &source_file, backup_source)) { - return GenerateTarget(target, source_file, patch, bonus, backup_source); - } - - LOG(ERROR) << "Failed to find any match"; - return false; -} - -bool FlashPartition(const Partition& partition, const std::string& source_filename) { - LOG(INFO) << "Flashing " << partition; - - // We try to load and check against the target hash first. - FileContents target_file; - if (ReadPartitionToBuffer(partition, &target_file, false)) { - // The early-exit case: the patch was already applied, this file has the desired hash, nothing - // for us to do. - LOG(INFO) << " already " << partition.hash.substr(0, 8); - return true; - } - - FileContents source_file; - if (!LoadFileContents(source_filename, &source_file)) { - LOG(ERROR) << "Failed to load source file"; - return false; - } - - uint8_t expected_sha1[SHA_DIGEST_LENGTH]; - if (ParseSha1(partition.hash, expected_sha1) != 0) { - LOG(ERROR) << "Failed to parse source hash \"" << partition.hash << "\""; - return false; - } - - if (memcmp(source_file.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) { - // The source doesn't have desired checksum. - LOG(ERROR) << "source \"" << source_filename << "\" doesn't have expected SHA-1 sum"; - LOG(ERROR) << "expected: " << partition.hash.substr(0, 8) - << ", found: " << short_sha1(source_file.sha1); - return false; - } - if (!WriteBufferToPartition(source_file, partition)) { - LOG(ERROR) << "Failed to write to " << partition; - return false; - } - return true; -} - -static bool GenerateTarget(const Partition& target, const FileContents& source_file, - const Value& patch, const Value* bonus_data, bool backup_source) { - uint8_t expected_sha1[SHA_DIGEST_LENGTH]; - if (ParseSha1(target.hash, expected_sha1) != 0) { - LOG(ERROR) << "Failed to parse target hash \"" << target.hash << "\""; - return false; - } - - if (patch.type != Value::Type::BLOB) { - LOG(ERROR) << "patch is not a blob"; - return false; - } - - const char* header = patch.data.data(); - size_t header_bytes_read = patch.data.size(); - bool use_bsdiff = false; - if (header_bytes_read >= 8 && memcmp(header, "BSDIFF40", 8) == 0) { - use_bsdiff = true; - } else if (header_bytes_read >= 8 && memcmp(header, "IMGDIFF2", 8) == 0) { - use_bsdiff = false; - } else { - LOG(ERROR) << "Unknown patch file format"; - return false; - } - - // We write the original source to cache, in case the partition write is interrupted. - if (backup_source && !CheckAndFreeSpaceOnCache(source_file.data.size())) { - LOG(ERROR) << "Not enough free space on /cache"; - return false; - } - if (backup_source && !SaveFileContents(Paths::Get().cache_temp_source(), &source_file)) { - LOG(ERROR) << "Failed to back up source file"; - return false; - } - - // We store the decoded output in memory. - FileContents patched; - SHA_CTX ctx; - SHA1_Init(&ctx); - SinkFn sink = [&patched, &ctx](const unsigned char* data, size_t len) { - SHA1_Update(&ctx, data, len); - patched.data.insert(patched.data.end(), data, data + len); - return len; - }; - - int result; - if (use_bsdiff) { - result = ApplyBSDiffPatch(source_file.data.data(), source_file.data.size(), patch, 0, sink); - } else { - result = - ApplyImagePatch(source_file.data.data(), source_file.data.size(), patch, sink, bonus_data); - } - - if (result != 0) { - LOG(ERROR) << "Failed to apply the patch: " << result; - return false; - } - - SHA1_Final(patched.sha1, &ctx); - if (memcmp(patched.sha1, expected_sha1, SHA_DIGEST_LENGTH) != 0) { - LOG(ERROR) << "Patching did not produce the expected SHA-1 of " << short_sha1(expected_sha1); - - LOG(ERROR) << "target size " << patched.data.size() << " SHA-1 " << short_sha1(patched.sha1); - LOG(ERROR) << "source size " << source_file.data.size() << " SHA-1 " - << short_sha1(source_file.sha1); - - uint8_t patch_digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast(patch.data.data()), patch.data.size(), patch_digest); - LOG(ERROR) << "patch size " << patch.data.size() << " SHA-1 " << short_sha1(patch_digest); - - if (bonus_data != nullptr) { - uint8_t bonus_digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast(bonus_data->data.data()), bonus_data->data.size(), - bonus_digest); - LOG(ERROR) << "bonus size " << bonus_data->data.size() << " SHA-1 " - << short_sha1(bonus_digest); - } - - return false; - } - - LOG(INFO) << " now " << short_sha1(expected_sha1); - - // Write back the temp file to the partition. - if (!WriteBufferToPartition(patched, target)) { - LOG(ERROR) << "Failed to write patched data to " << target.name; - return false; - } - - // Delete the backup copy of the source. - if (backup_source) { - unlink(Paths::Get().cache_temp_source().c_str()); - } - - // Success! - return true; -} - -bool CheckPartition(const Partition& partition) { - FileContents target_file; - return ReadPartitionToBuffer(partition, &target_file, false); -} - -Partition Partition::Parse(const std::string& input_str, std::string* err) { - std::vector pieces = android::base::Split(input_str, ":"); - if (pieces.size() != 4 || pieces[0] != "EMMC") { - *err = "Invalid number of tokens or non-eMMC target"; - return {}; - } - - size_t size; - if (!android::base::ParseUint(pieces[2], &size) || size == 0) { - *err = "Failed to parse \"" + pieces[2] + "\" as byte count"; - return {}; - } - - return Partition(pieces[1], size, pieces[3]); -} - -std::string Partition::ToString() const { - if (*this) { - return "EMMC:"s + name + ":" + std::to_string(size) + ":" + hash; - } - return ""; -} - -std::ostream& operator<<(std::ostream& os, const Partition& partition) { - os << partition.ToString(); - return os; -} diff --git a/applypatch/applypatch_main.cpp b/applypatch/applypatch_main.cpp deleted file mode 100644 index 92d2b3fa..00000000 --- a/applypatch/applypatch_main.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "applypatch_modes.h" - -#include - -// See the comments for applypatch() function. -int main(int argc, char** argv) { - android::base::InitLogging(argv); - return applypatch_modes(argc, argv); -} diff --git a/applypatch/applypatch_modes.cpp b/applypatch/applypatch_modes.cpp deleted file mode 100644 index bb5eeae9..00000000 --- a/applypatch/applypatch_modes.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "applypatch_modes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "applypatch/applypatch.h" -#include "edify/expr.h" - -static int CheckMode(const std::string& target_emmc) { - std::string err; - auto target = Partition::Parse(target_emmc, &err); - if (!target) { - LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err; - return 2; - } - return CheckPartition(target) ? 0 : 1; -} - -static int FlashMode(const std::string& target_emmc, const std::string& source_file) { - std::string err; - auto target = Partition::Parse(target_emmc, &err); - if (!target) { - LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err; - return 2; - } - return FlashPartition(target, source_file) ? 0 : 1; -} - -static int PatchMode(const std::string& target_emmc, const std::string& source_emmc, - const std::string& patch_file, const std::string& bonus_file) { - std::string err; - auto target = Partition::Parse(target_emmc, &err); - if (!target) { - LOG(ERROR) << "Failed to parse target \"" << target_emmc << "\": " << err; - return 2; - } - - auto source = Partition::Parse(source_emmc, &err); - if (!source) { - LOG(ERROR) << "Failed to parse source \"" << source_emmc << "\": " << err; - return 2; - } - - std::string patch_contents; - if (!android::base::ReadFileToString(patch_file, &patch_contents)) { - PLOG(ERROR) << "Failed to read patch file \"" << patch_file << "\""; - return 1; - } - - Value patch(Value::Type::BLOB, std::move(patch_contents)); - std::unique_ptr bonus; - if (!bonus_file.empty()) { - std::string bonus_contents; - if (!android::base::ReadFileToString(bonus_file, &bonus_contents)) { - PLOG(ERROR) << "Failed to read bonus file \"" << bonus_file << "\""; - return 1; - } - bonus = std::make_unique(Value::Type::BLOB, std::move(bonus_contents)); - } - - return PatchPartition(target, source, patch, bonus.get(), false) ? 0 : 1; -} - -static void Usage() { - printf( - "Usage: \n" - "check mode\n" - " applypatch --check EMMC:::\n\n" - "flash mode\n" - " applypatch --flash \n" - " --target EMMC:::\n\n" - "patch mode\n" - " applypatch [--bonus ]\n" - " --patch \n" - " --target EMMC:::\n" - " --source EMMC:::\n\n" - "show license\n" - " applypatch --license\n" - "\n\n"); -} - -int applypatch_modes(int argc, char* argv[]) { - static constexpr struct option OPTIONS[]{ - // clang-format off - { "bonus", required_argument, nullptr, 0 }, - { "check", required_argument, nullptr, 0 }, - { "flash", required_argument, nullptr, 0 }, - { "license", no_argument, nullptr, 0 }, - { "patch", required_argument, nullptr, 0 }, - { "source", required_argument, nullptr, 0 }, - { "target", required_argument, nullptr, 0 }, - { nullptr, 0, nullptr, 0 }, - // clang-format on - }; - - std::string check_target; - std::string source; - std::string target; - std::string patch; - std::string bonus; - - bool check_mode = false; - bool flash_mode = false; - bool patch_mode = false; - - optind = 1; - - int arg; - int option_index; - while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) { - switch (arg) { - case 0: { - std::string option = OPTIONS[option_index].name; - if (option == "bonus") { - bonus = optarg; - } else if (option == "check") { - check_target = optarg; - check_mode = true; - } else if (option == "flash") { - source = optarg; - flash_mode = true; - } else if (option == "license") { - return ShowLicenses(); - } else if (option == "patch") { - patch = optarg; - patch_mode = true; - } else if (option == "source") { - source = optarg; - } else if (option == "target") { - target = optarg; - } - break; - } - case '?': - default: - LOG(ERROR) << "Invalid argument"; - Usage(); - return 2; - } - } - - if (check_mode) { - return CheckMode(check_target); - } - if (flash_mode) { - if (!bonus.empty()) { - LOG(ERROR) << "bonus file not supported in flash mode"; - return 1; - } - return FlashMode(target, source); - } - if (patch_mode) { - return PatchMode(target, source, patch, bonus); - } - - Usage(); - return 2; -} diff --git a/applypatch/applypatch_modes.h b/applypatch/applypatch_modes.h deleted file mode 100644 index aa60a431..00000000 --- a/applypatch/applypatch_modes.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _APPLYPATCH_MODES_H -#define _APPLYPATCH_MODES_H - -int applypatch_modes(int argc, char* argv[]); - -#endif // _APPLYPATCH_MODES_H diff --git a/applypatch/bspatch.cpp b/applypatch/bspatch.cpp deleted file mode 100644 index ba33c3a9..00000000 --- a/applypatch/bspatch.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// This file is a nearly line-for-line copy of bspatch.c from the -// bsdiff-4.3 distribution; the primary differences being how the -// input and output data are read and the error handling. Running -// applypatch with the -l option will display the bsdiff license -// notice. - -#include -#include - -#include - -#include -#include -#include - -#include "applypatch/applypatch.h" -#include "edify/expr.h" -#include "otautil/print_sha1.h" - -void ShowBSDiffLicense() { - puts("The bsdiff library used herein is:\n" - "\n" - "Copyright 2003-2005 Colin Percival\n" - "All rights reserved\n" - "\n" - "Redistribution and use in source and binary forms, with or without\n" - "modification, are permitted providing that the following conditions\n" - "are met:\n" - "1. Redistributions of source code must retain the above copyright\n" - " notice, this list of conditions and the following disclaimer.\n" - "2. Redistributions in binary form must reproduce the above copyright\n" - " notice, this list of conditions and the following disclaimer in the\n" - " documentation and/or other materials provided with the distribution.\n" - "\n" - "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n" - "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n" - "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n" - "ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n" - "DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n" - "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n" - "OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n" - "HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n" - "STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n" - "IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n" - "POSSIBILITY OF SUCH DAMAGE.\n" - "\n------------------\n\n" - "This program uses Julian R Seward's \"libbzip2\" library, available\n" - "from http://www.bzip.org/.\n" - ); -} - -int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value& patch, - size_t patch_offset, SinkFn sink) { - CHECK_LE(patch_offset, patch.data.size()); - - int result = bsdiff::bspatch(old_data, old_size, - reinterpret_cast(&patch.data[patch_offset]), - patch.data.size() - patch_offset, sink); - if (result != 0) { - LOG(ERROR) << "bspatch failed, result: " << result; - // print SHA1 of the patch in the case of a data error. - if (result == 2) { - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast(patch.data.data() + patch_offset), - patch.data.size() - patch_offset, digest); - std::string patch_sha1 = print_sha1(digest); - LOG(ERROR) << "Patch may be corrupted, offset: " << patch_offset << ", SHA1: " << patch_sha1; - } - } - return result; -} diff --git a/applypatch/freecache.cpp b/applypatch/freecache.cpp deleted file mode 100644 index 3868ef23..00000000 --- a/applypatch/freecache.cpp +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "applypatch/applypatch.h" -#include "otautil/paths.h" - -static int EliminateOpenFiles(const std::string& dirname, std::set* files) { - std::unique_ptr d(opendir("/proc"), closedir); - if (!d) { - PLOG(ERROR) << "Failed to open /proc"; - return -1; - } - struct dirent* de; - while ((de = readdir(d.get())) != 0) { - unsigned int pid; - if (!android::base::ParseUint(de->d_name, &pid)) { - continue; - } - std::string path = android::base::StringPrintf("/proc/%s/fd/", de->d_name); - - struct dirent* fdde; - std::unique_ptr fdd(opendir(path.c_str()), closedir); - if (!fdd) { - PLOG(ERROR) << "Failed to open " << path; - continue; - } - while ((fdde = readdir(fdd.get())) != 0) { - std::string fd_path = path + fdde->d_name; - char link[FILENAME_MAX]; - - int count = readlink(fd_path.c_str(), link, sizeof(link)-1); - if (count >= 0) { - link[count] = '\0'; - if (android::base::StartsWith(link, dirname)) { - if (files->erase(link) > 0) { - LOG(INFO) << link << " is open by " << de->d_name; - } - } - } - } - } - return 0; -} - -static std::vector FindExpendableFiles( - const std::string& dirname, const std::function& name_filter) { - std::unique_ptr d(opendir(dirname.c_str()), closedir); - if (!d) { - PLOG(ERROR) << "Failed to open " << dirname; - return {}; - } - - // Look for regular files in the directory (not in any subdirectories). - std::set files; - struct dirent* de; - while ((de = readdir(d.get())) != 0) { - std::string path = dirname + "/" + de->d_name; - - // We can't delete cache_temp_source; if it's there we might have restarted during - // installation and could be depending on it to be there. - if (path == Paths::Get().cache_temp_source()) { - continue; - } - - // Do not delete the file if it doesn't have the expected format. - if (name_filter != nullptr && !name_filter(de->d_name)) { - continue; - } - - struct stat st; - if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) { - files.insert(path); - } - } - - LOG(INFO) << files.size() << " regular files in deletable directory"; - if (EliminateOpenFiles(dirname, &files) < 0) { - return {}; - } - - return std::vector(files.begin(), files.end()); -} - -// Parses the index of given log file, e.g. 3 for last_log.3; returns max number if the log name -// doesn't have the expected format so that we'll delete these ones first. -static unsigned int GetLogIndex(const std::string& log_name) { - if (log_name == "last_log" || log_name == "last_kmsg") { - return 0; - } - - unsigned int index; - if (sscanf(log_name.c_str(), "last_log.%u", &index) == 1 || - sscanf(log_name.c_str(), "last_kmsg.%u", &index) == 1) { - return index; - } - - return std::numeric_limits::max(); -} - -// Returns the amount of free space (in bytes) on the filesystem containing filename, or -1 on -// error. -static int64_t FreeSpaceForFile(const std::string& filename) { - struct statfs sf; - if (statfs(filename.c_str(), &sf) == -1) { - PLOG(ERROR) << "Failed to statfs " << filename; - return -1; - } - - auto f_bsize = static_cast(sf.f_bsize); - auto free_space = sf.f_bsize * sf.f_bavail; - if (f_bsize == 0 || free_space / f_bsize != static_cast(sf.f_bavail)) { - LOG(ERROR) << "Invalid block size or overflow (sf.f_bsize " << sf.f_bsize << ", sf.f_bavail " - << sf.f_bavail << ")"; - return -1; - } - return free_space; -} - -bool CheckAndFreeSpaceOnCache(size_t bytes) { -#ifndef __ANDROID__ - // TODO(xunchang): Implement a heuristic cache size check during host simulation. - LOG(WARNING) << "Skipped making (" << bytes - << ") bytes free space on /cache; program is running on host"; - return true; -#endif - - std::vector dirs{ "/cache", Paths::Get().cache_log_directory() }; - for (const auto& dirname : dirs) { - if (RemoveFilesInDirectory(bytes, dirname, FreeSpaceForFile)) { - return true; - } - } - - return false; -} - -bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname, - const std::function& space_checker) { - // The requested size cannot exceed max int64_t. - if (static_cast(bytes_needed) > - static_cast(std::numeric_limits::max())) { - LOG(ERROR) << "Invalid arg of bytes_needed: " << bytes_needed; - return false; - } - - struct stat st; - if (stat(dirname.c_str(), &st) == -1) { - PLOG(ERROR) << "Failed to stat " << dirname; - return false; - } - if (!S_ISDIR(st.st_mode)) { - LOG(ERROR) << dirname << " is not a directory"; - return false; - } - - int64_t free_now = space_checker(dirname); - if (free_now == -1) { - LOG(ERROR) << "Failed to check free space for " << dirname; - return false; - } - LOG(INFO) << free_now << " bytes free on " << dirname << " (" << bytes_needed << " needed)"; - - if (free_now >= static_cast(bytes_needed)) { - return true; - } - - std::vector files; - if (dirname == Paths::Get().cache_log_directory()) { - // Deletes the log files only. - auto log_filter = [](const std::string& file_name) { - return android::base::StartsWith(file_name, "last_log") || - android::base::StartsWith(file_name, "last_kmsg"); - }; - - files = FindExpendableFiles(dirname, log_filter); - - // Older logs will come to the top of the queue. - auto comparator = [](const std::string& name1, const std::string& name2) -> bool { - unsigned int index1 = GetLogIndex(android::base::Basename(name1)); - unsigned int index2 = GetLogIndex(android::base::Basename(name2)); - if (index1 == index2) { - return name1 < name2; - } - - return index1 > index2; - }; - - std::sort(files.begin(), files.end(), comparator); - } else { - // We're allowed to delete unopened regular files in the directory. - files = FindExpendableFiles(dirname, nullptr); - } - - for (const auto& file : files) { - if (unlink(file.c_str()) == -1) { - PLOG(ERROR) << "Failed to delete " << file; - continue; - } - - free_now = space_checker(dirname); - if (free_now == -1) { - LOG(ERROR) << "Failed to check free space for " << dirname; - return false; - } - LOG(INFO) << "Deleted " << file << "; now " << free_now << " bytes free"; - if (free_now >= static_cast(bytes_needed)) { - return true; - } - } - - return false; -} diff --git a/applypatch/imgdiff.cpp b/applypatch/imgdiff.cpp deleted file mode 100644 index 33ed3300..00000000 --- a/applypatch/imgdiff.cpp +++ /dev/null @@ -1,1641 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * This program constructs binary patches for images -- such as boot.img and recovery.img -- that - * consist primarily of large chunks of gzipped data interspersed with uncompressed data. Doing a - * naive bsdiff of these files is not useful because small changes in the data lead to large - * changes in the compressed bitstream; bsdiff patches of gzipped data are typically as large as - * the data itself. - * - * To patch these usefully, we break the source and target images up into chunks of two types: - * "normal" and "gzip". Normal chunks are simply patched using a plain bsdiff. Gzip chunks are - * first expanded, then a bsdiff is applied to the uncompressed data, then the patched data is - * gzipped using the same encoder parameters. Patched chunks are concatenated together to create - * the output file; the output image should be *exactly* the same series of bytes as the target - * image used originally to generate the patch. - * - * To work well with this tool, the gzipped sections of the target image must have been generated - * using the same deflate encoder that is available in applypatch, namely, the one in the zlib - * library. In practice this means that images should be compressed using the toybox "gzip" toy, - * not the GNU gzip program. - * - * An "imgdiff" patch consists of a header describing the chunk structure of the file and any - * encoding parameters needed for the gzipped chunks, followed by N bsdiff patches, one per chunk. - * - * For a diff to be generated, the source and target must be in well-formed zip archive format; - * or they are image files with the same "chunk" structure: that is, the same number of gzipped and - * normal chunks in the same order. Android boot and recovery images currently consist of five - * chunks: a small normal header, a gzipped kernel, a small normal section, a gzipped ramdisk, and - * finally a small normal footer. - * - * Caveats: we locate gzipped sections within the source and target images by searching for the - * byte sequence 1f8b0800: 1f8b is the gzip magic number; 08 specifies the "deflate" encoding - * [the only encoding supported by the gzip standard]; and 00 is the flags byte. We do not - * currently support any extra header fields (which would be indicated by a nonzero flags byte). - * We also don't handle the case when that byte sequence appears spuriously in the file. (Note - * that it would have to occur spuriously within a normal chunk to be a problem.) - * - * - * The imgdiff patch header looks like this: - * - * "IMGDIFF2" (8) [magic number and version] - * chunk count (4) - * for each chunk: - * chunk type (4) [CHUNK_{NORMAL, GZIP, DEFLATE, RAW}] - * if chunk type == CHUNK_NORMAL: - * source start (8) - * source len (8) - * bsdiff patch offset (8) [from start of patch file] - * if chunk type == CHUNK_GZIP: (version 1 only) - * source start (8) - * source len (8) - * bsdiff patch offset (8) [from start of patch file] - * source expanded len (8) [size of uncompressed source] - * target expected len (8) [size of uncompressed target] - * gzip level (4) - * method (4) - * windowBits (4) - * memLevel (4) - * strategy (4) - * gzip header len (4) - * gzip header (gzip header len) - * gzip footer (8) - * if chunk type == CHUNK_DEFLATE: (version 2 only) - * source start (8) - * source len (8) - * bsdiff patch offset (8) [from start of patch file] - * source expanded len (8) [size of uncompressed source] - * target expected len (8) [size of uncompressed target] - * gzip level (4) - * method (4) - * windowBits (4) - * memLevel (4) - * strategy (4) - * if chunk type == RAW: (version 2 only) - * target len (4) - * data (target len) - * - * All integers are little-endian. "source start" and "source len" specify the section of the - * input image that comprises this chunk, including the gzip header and footer for gzip chunks. - * "source expanded len" is the size of the uncompressed source data. "target expected len" is the - * size of the uncompressed data after applying the bsdiff patch. The next five parameters - * specify the zlib parameters to be used when compressing the patched data, and the next three - * specify the header and footer to be wrapped around the compressed data to create the output - * chunk (so that header contents like the timestamp are recreated exactly). - * - * After the header there are 'chunk count' bsdiff patches; the offset of each from the beginning - * of the file is specified in the header. - * - * This tool can take an optional file of "bonus data". This is an extra file of data that is - * appended to chunk #1 after it is compressed (it must be a CHUNK_DEFLATE chunk). The same file - * must be available (and passed to applypatch with -b) when applying the patch. This is used to - * reduce the size of recovery-from-boot patches by combining the boot image with recovery ramdisk - * information that is stored on the system partition. - * - * When generating the patch between two zip files, this tool has an option "--block-limit" to - * split the large source/target files into several pair of pieces, with each piece has at most - * *limit* blocks. When this option is used, we also need to output the split info into the file - * path specified by "--split-info". - * - * Format of split info file: - * 2 [version of imgdiff] - * n [count of split pieces] - * , , [size and ranges for split piece#1] - * ... - * , , [size and ranges for split piece#n] - * - * To split a pair of large zip files, we walk through the chunks in target zip and search by its - * entry_name in the source zip. If the entry_name is non-empty and a matching entry in source - * is found, we'll add the source entry to the current split source image; otherwise we'll skip - * this chunk and later do bsdiff between all the skipped trunks and the whole split source image. - * We move on to the next pair of pieces if the size of the split source image reaches the block - * limit. - * - * After the split, the target pieces are continuous and block aligned, while the source pieces - * are mutually exclusive. Some of the source blocks may not be used if there's no matching - * entry_name in the target; as a result, they won't be included in any of these split source - * images. Then we will generate patches accordingly between each split image pairs; in particular, - * the unmatched trunks in the split target will diff against the entire split source image. - * - * For example: - * Input: [src_image, tgt_image] - * Split: [src-0, tgt-0; src-1, tgt-1, src-2, tgt-2] - * Diff: [ patch-0; patch-1; patch-2] - * - * Patch: [(src-0, patch-0) = tgt-0; (src-1, patch-1) = tgt-1; (src-2, patch-2) = tgt-2] - * Concatenate: [tgt-0 + tgt-1 + tgt-2 = tgt_image] - */ - -#include "applypatch/imgdiff.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "applypatch/imgdiff_image.h" -#include "otautil/rangeset.h" - -using android::base::get_unaligned; - -static constexpr size_t VERSION = 2; - -// We assume the header "IMGDIFF#" is 8 bytes. -static_assert(VERSION <= 9, "VERSION occupies more than one byte"); - -static constexpr size_t BLOCK_SIZE = 4096; -static constexpr size_t BUFFER_SIZE = 0x8000; - -// If we use this function to write the offset and length (type size_t), their values should not -// exceed 2^63; because the signed bit will be casted away. -static inline bool Write8(int fd, int64_t value) { - return android::base::WriteFully(fd, &value, sizeof(int64_t)); -} - -// Similarly, the value should not exceed 2^31 if we are casting from size_t (e.g. target chunk -// size). -static inline bool Write4(int fd, int32_t value) { - return android::base::WriteFully(fd, &value, sizeof(int32_t)); -} - -// Trim the head or tail to align with the block size. Return false if the chunk has nothing left -// after alignment. -static bool AlignHead(size_t* start, size_t* length) { - size_t residual = (*start % BLOCK_SIZE == 0) ? 0 : BLOCK_SIZE - *start % BLOCK_SIZE; - - if (*length <= residual) { - *length = 0; - return false; - } - - // Trim the data in the beginning. - *start += residual; - *length -= residual; - return true; -} - -static bool AlignTail(size_t* start, size_t* length) { - size_t residual = (*start + *length) % BLOCK_SIZE; - if (*length <= residual) { - *length = 0; - return false; - } - - // Trim the data in the end. - *length -= residual; - return true; -} - -// Remove the used blocks from the source chunk to make sure the source ranges are mutually -// exclusive after split. Return false if we fail to get the non-overlapped ranges. In such -// a case, we'll skip the entire source chunk. -static bool RemoveUsedBlocks(size_t* start, size_t* length, const SortedRangeSet& used_ranges) { - if (!used_ranges.Overlaps(*start, *length)) { - return true; - } - - // TODO find the largest non-overlap chunk. - LOG(INFO) << "Removing block " << used_ranges.ToString() << " from " << *start << " - " - << *start + *length - 1; - - // If there's no duplicate entry name, we should only overlap in the head or tail block. Try to - // trim both blocks. Skip this source chunk in case it still overlaps with the used ranges. - if (AlignHead(start, length) && !used_ranges.Overlaps(*start, *length)) { - return true; - } - if (AlignTail(start, length) && !used_ranges.Overlaps(*start, *length)) { - return true; - } - - LOG(WARNING) << "Failed to remove the overlapped block ranges; skip the source"; - return false; -} - -static const struct option OPTIONS[] = { - { "zip-mode", no_argument, nullptr, 'z' }, - { "bonus-file", required_argument, nullptr, 'b' }, - { "block-limit", required_argument, nullptr, 0 }, - { "debug-dir", required_argument, nullptr, 0 }, - { "split-info", required_argument, nullptr, 0 }, - { "verbose", no_argument, nullptr, 'v' }, - { nullptr, 0, nullptr, 0 }, -}; - -ImageChunk::ImageChunk(int type, size_t start, const std::vector* file_content, - size_t raw_data_len, std::string entry_name) - : type_(type), - start_(start), - input_file_ptr_(file_content), - raw_data_len_(raw_data_len), - compress_level_(6), - entry_name_(std::move(entry_name)) { - CHECK(file_content != nullptr) << "input file container can't be nullptr"; -} - -const uint8_t* ImageChunk::GetRawData() const { - CHECK_LE(start_ + raw_data_len_, input_file_ptr_->size()); - return input_file_ptr_->data() + start_; -} - -const uint8_t * ImageChunk::DataForPatch() const { - if (type_ == CHUNK_DEFLATE) { - return uncompressed_data_.data(); - } - return GetRawData(); -} - -size_t ImageChunk::DataLengthForPatch() const { - if (type_ == CHUNK_DEFLATE) { - return uncompressed_data_.size(); - } - return raw_data_len_; -} - -void ImageChunk::Dump(size_t index) const { - LOG(INFO) << "chunk: " << index << ", type: " << type_ << ", start: " << start_ - << ", len: " << DataLengthForPatch() << ", name: " << entry_name_; -} - -bool ImageChunk::operator==(const ImageChunk& other) const { - if (type_ != other.type_) { - return false; - } - return (raw_data_len_ == other.raw_data_len_ && - memcmp(GetRawData(), other.GetRawData(), raw_data_len_) == 0); -} - -void ImageChunk::SetUncompressedData(std::vector data) { - uncompressed_data_ = std::move(data); -} - -bool ImageChunk::SetBonusData(const std::vector& bonus_data) { - if (type_ != CHUNK_DEFLATE) { - return false; - } - uncompressed_data_.insert(uncompressed_data_.end(), bonus_data.begin(), bonus_data.end()); - return true; -} - -void ImageChunk::ChangeDeflateChunkToNormal() { - if (type_ != CHUNK_DEFLATE) return; - type_ = CHUNK_NORMAL; - // No need to clear the entry name. - uncompressed_data_.clear(); -} - -bool ImageChunk::IsAdjacentNormal(const ImageChunk& other) const { - if (type_ != CHUNK_NORMAL || other.type_ != CHUNK_NORMAL) { - return false; - } - return (other.start_ == start_ + raw_data_len_); -} - -void ImageChunk::MergeAdjacentNormal(const ImageChunk& other) { - CHECK(IsAdjacentNormal(other)); - raw_data_len_ = raw_data_len_ + other.raw_data_len_; -} - -bool ImageChunk::MakePatch(const ImageChunk& tgt, const ImageChunk& src, - std::vector* patch_data, - bsdiff::SuffixArrayIndexInterface** bsdiff_cache) { -#if defined(__ANDROID__) - char ptemp[] = "/data/local/tmp/imgdiff-patch-XXXXXX"; -#else - char ptemp[] = "/tmp/imgdiff-patch-XXXXXX"; -#endif - - int fd = mkstemp(ptemp); - if (fd == -1) { - PLOG(ERROR) << "MakePatch failed to create a temporary file"; - return false; - } - close(fd); - - int r = bsdiff::bsdiff(src.DataForPatch(), src.DataLengthForPatch(), tgt.DataForPatch(), - tgt.DataLengthForPatch(), ptemp, bsdiff_cache); - if (r != 0) { - LOG(ERROR) << "bsdiff() failed: " << r; - return false; - } - - android::base::unique_fd patch_fd(open(ptemp, O_RDONLY)); - if (patch_fd == -1) { - PLOG(ERROR) << "Failed to open " << ptemp; - return false; - } - struct stat st; - if (fstat(patch_fd, &st) != 0) { - PLOG(ERROR) << "Failed to stat patch file " << ptemp; - return false; - } - - size_t sz = static_cast(st.st_size); - - patch_data->resize(sz); - if (!android::base::ReadFully(patch_fd, patch_data->data(), sz)) { - PLOG(ERROR) << "Failed to read " << ptemp; - unlink(ptemp); - return false; - } - - unlink(ptemp); - - return true; -} - -bool ImageChunk::ReconstructDeflateChunk() { - if (type_ != CHUNK_DEFLATE) { - LOG(ERROR) << "Attempted to reconstruct non-deflate chunk"; - return false; - } - - // We only check two combinations of encoder parameters: level 6 (the default) and level 9 - // (the maximum). - for (int level = 6; level <= 9; level += 3) { - if (TryReconstruction(level)) { - compress_level_ = level; - return true; - } - } - - return false; -} - -/* - * Takes the uncompressed data stored in the chunk, compresses it using the zlib parameters stored - * in the chunk, and checks that it matches exactly the compressed data we started with (also - * stored in the chunk). - */ -bool ImageChunk::TryReconstruction(int level) { - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = uncompressed_data_.size(); - strm.next_in = uncompressed_data_.data(); - int ret = deflateInit2(&strm, level, METHOD, WINDOWBITS, MEMLEVEL, STRATEGY); - if (ret < 0) { - LOG(ERROR) << "Failed to initialize deflate: " << ret; - return false; - } - - std::vector buffer(BUFFER_SIZE); - size_t offset = 0; - do { - strm.avail_out = buffer.size(); - strm.next_out = buffer.data(); - ret = deflate(&strm, Z_FINISH); - if (ret < 0) { - LOG(ERROR) << "Failed to deflate: " << ret; - return false; - } - - size_t compressed_size = buffer.size() - strm.avail_out; - if (memcmp(buffer.data(), input_file_ptr_->data() + start_ + offset, compressed_size) != 0) { - // mismatch; data isn't the same. - deflateEnd(&strm); - return false; - } - offset += compressed_size; - } while (ret != Z_STREAM_END); - deflateEnd(&strm); - - if (offset != raw_data_len_) { - // mismatch; ran out of data before we should have. - return false; - } - return true; -} - -PatchChunk::PatchChunk(const ImageChunk& tgt, const ImageChunk& src, std::vector data) - : type_(tgt.GetType()), - source_start_(src.GetStartOffset()), - source_len_(src.GetRawDataLength()), - source_uncompressed_len_(src.DataLengthForPatch()), - target_start_(tgt.GetStartOffset()), - target_len_(tgt.GetRawDataLength()), - target_uncompressed_len_(tgt.DataLengthForPatch()), - target_compress_level_(tgt.GetCompressLevel()), - data_(std::move(data)) {} - -// Construct a CHUNK_RAW patch from the target data directly. -PatchChunk::PatchChunk(const ImageChunk& tgt) - : type_(CHUNK_RAW), - source_start_(0), - source_len_(0), - source_uncompressed_len_(0), - target_start_(tgt.GetStartOffset()), - target_len_(tgt.GetRawDataLength()), - target_uncompressed_len_(tgt.DataLengthForPatch()), - target_compress_level_(tgt.GetCompressLevel()), - data_(tgt.GetRawData(), tgt.GetRawData() + tgt.GetRawDataLength()) {} - -// Return true if raw data is smaller than the patch size. -bool PatchChunk::RawDataIsSmaller(const ImageChunk& tgt, size_t patch_size) { - size_t target_len = tgt.GetRawDataLength(); - return target_len < patch_size || (tgt.GetType() == CHUNK_NORMAL && target_len <= 160); -} - -void PatchChunk::UpdateSourceOffset(const SortedRangeSet& src_range) { - if (type_ == CHUNK_DEFLATE) { - source_start_ = src_range.GetOffsetInRangeSet(source_start_); - } -} - -// Header size: -// header_type 4 bytes -// CHUNK_NORMAL 8*3 = 24 bytes -// CHUNK_DEFLATE 8*5 + 4*5 = 60 bytes -// CHUNK_RAW 4 bytes + patch_size -size_t PatchChunk::GetHeaderSize() const { - switch (type_) { - case CHUNK_NORMAL: - return 4 + 8 * 3; - case CHUNK_DEFLATE: - return 4 + 8 * 5 + 4 * 5; - case CHUNK_RAW: - return 4 + 4 + data_.size(); - default: - CHECK(false) << "unexpected chunk type: " << type_; // Should not reach here. - return 0; - } -} - -// Return the offset of the next patch into the patch data. -size_t PatchChunk::WriteHeaderToFd(int fd, size_t offset, size_t index) const { - Write4(fd, type_); - switch (type_) { - case CHUNK_NORMAL: - LOG(INFO) << android::base::StringPrintf("chunk %zu: normal (%10zu, %10zu) %10zu", index, - target_start_, target_len_, data_.size()); - Write8(fd, static_cast(source_start_)); - Write8(fd, static_cast(source_len_)); - Write8(fd, static_cast(offset)); - return offset + data_.size(); - case CHUNK_DEFLATE: - LOG(INFO) << android::base::StringPrintf("chunk %zu: deflate (%10zu, %10zu) %10zu", index, - target_start_, target_len_, data_.size()); - Write8(fd, static_cast(source_start_)); - Write8(fd, static_cast(source_len_)); - Write8(fd, static_cast(offset)); - Write8(fd, static_cast(source_uncompressed_len_)); - Write8(fd, static_cast(target_uncompressed_len_)); - Write4(fd, target_compress_level_); - Write4(fd, ImageChunk::METHOD); - Write4(fd, ImageChunk::WINDOWBITS); - Write4(fd, ImageChunk::MEMLEVEL); - Write4(fd, ImageChunk::STRATEGY); - return offset + data_.size(); - case CHUNK_RAW: - LOG(INFO) << android::base::StringPrintf("chunk %zu: raw (%10zu, %10zu)", index, - target_start_, target_len_); - Write4(fd, static_cast(data_.size())); - if (!android::base::WriteFully(fd, data_.data(), data_.size())) { - CHECK(false) << "Failed to write " << data_.size() << " bytes patch"; - } - return offset; - default: - CHECK(false) << "unexpected chunk type: " << type_; - return offset; - } -} - -size_t PatchChunk::PatchSize() const { - if (type_ == CHUNK_RAW) { - return GetHeaderSize(); - } - return GetHeaderSize() + data_.size(); -} - -// Write the contents of |patch_chunks| to |patch_fd|. -bool PatchChunk::WritePatchDataToFd(const std::vector& patch_chunks, int patch_fd) { - // Figure out how big the imgdiff file header is going to be, so that we can correctly compute - // the offset of each bsdiff patch within the file. - size_t total_header_size = 12; - for (const auto& patch : patch_chunks) { - total_header_size += patch.GetHeaderSize(); - } - - size_t offset = total_header_size; - - // Write out the headers. - if (!android::base::WriteStringToFd("IMGDIFF" + std::to_string(VERSION), patch_fd)) { - PLOG(ERROR) << "Failed to write \"IMGDIFF" << VERSION << "\""; - return false; - } - - Write4(patch_fd, static_cast(patch_chunks.size())); - LOG(INFO) << "Writing " << patch_chunks.size() << " patch headers..."; - for (size_t i = 0; i < patch_chunks.size(); ++i) { - offset = patch_chunks[i].WriteHeaderToFd(patch_fd, offset, i); - } - - // Append each chunk's bsdiff patch, in order. - for (const auto& patch : patch_chunks) { - if (patch.type_ == CHUNK_RAW) { - continue; - } - if (!android::base::WriteFully(patch_fd, patch.data_.data(), patch.data_.size())) { - PLOG(ERROR) << "Failed to write " << patch.data_.size() << " bytes patch to patch_fd"; - return false; - } - } - - return true; -} - -ImageChunk& Image::operator[](size_t i) { - CHECK_LT(i, chunks_.size()); - return chunks_[i]; -} - -const ImageChunk& Image::operator[](size_t i) const { - CHECK_LT(i, chunks_.size()); - return chunks_[i]; -} - -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()); - } -} - -void Image::DumpChunks() const { - std::string type = is_source_ ? "source" : "target"; - LOG(INFO) << "Dumping chunks for " << type; - for (size_t i = 0; i < chunks_.size(); ++i) { - chunks_[i].Dump(i); - } -} - -bool Image::ReadFile(const std::string& filename, std::vector* file_content) { - CHECK(file_content != nullptr); - - android::base::unique_fd fd(open(filename.c_str(), O_RDONLY)); - if (fd == -1) { - PLOG(ERROR) << "Failed to open " << filename; - return false; - } - struct stat st; - if (fstat(fd, &st) != 0) { - PLOG(ERROR) << "Failed to stat " << filename; - return false; - } - - size_t sz = static_cast(st.st_size); - file_content->resize(sz); - if (!android::base::ReadFully(fd, file_content->data(), sz)) { - PLOG(ERROR) << "Failed to read " << filename; - return false; - } - fd.reset(); - - return true; -} - -bool ZipModeImage::Initialize(const std::string& filename) { - if (!ReadFile(filename, &file_content_)) { - return false; - } - - // Omit the trailing zeros before we pass the file to ziparchive handler. - size_t zipfile_size; - if (!GetZipFileSize(&zipfile_size)) { - LOG(ERROR) << "Failed to parse the actual size of " << filename; - return false; - } - ZipArchiveHandle handle; - int err = OpenArchiveFromMemory(const_cast(file_content_.data()), zipfile_size, - filename.c_str(), &handle); - if (err != 0) { - LOG(ERROR) << "Failed to open zip file " << filename << ": " << ErrorCodeString(err); - CloseArchive(handle); - return false; - } - - 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); - if (ret != 0) { - LOG(ERROR) << "Failed to iterate over entries in " << filename << ": " << ErrorCodeString(ret); - return false; - } - - // Create a list of deflated zip entries, sorted by offset. - std::vector> temp_entries; - std::string name; - ZipEntry64 entry; - while ((ret = Next(cookie, &entry, &name)) == 0) { - if (entry.method == kCompressDeflated || limit_ > 0) { - temp_entries.emplace_back(name, entry); - } - } - - if (ret != -1) { - LOG(ERROR) << "Error while iterating over zip entries: " << ErrorCodeString(ret); - return false; - } - std::sort(temp_entries.begin(), temp_entries.end(), - [](auto& entry1, auto& entry2) { return entry1.second.offset < entry2.second.offset; }); - - EndIteration(cookie); - - // 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)) { - LOG(ERROR) << "Failed to add " << entry.first << " to source chunks"; - return false; - } - } - - // Add the end of zip file (mainly central directory) as a normal chunk. - size_t entries_end = 0; - if (!temp_entries.empty()) { - CHECK_GE(temp_entries.back().second.offset, 0); - if (__builtin_add_overflow(temp_entries.back().second.offset, - temp_entries.back().second.compressed_length, &entries_end)) { - LOG(ERROR) << "`entries_end` overflows on entry with offset " - << temp_entries.back().second.offset << " and compressed_length " - << temp_entries.back().second.compressed_length; - return false; - } - } - CHECK_LT(entries_end, file_content_.size()); - chunks_.emplace_back(CHUNK_NORMAL, entries_end, &file_content_, - file_content_.size() - entries_end); - - 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 < file_content_.size()) { - if (nextentry < temp_entries.size() && - static_cast(pos) == temp_entries[nextentry].second.offset) { - // Add the next zip entry. - std::string entry_name = temp_entries[nextentry].first; - if (!AddZipEntryToChunks(handle, entry_name, &temp_entries[nextentry].second)) { - LOG(ERROR) << "Failed to add " << entry_name << " to target chunks"; - return false; - } - if (temp_entries[nextentry].second.compressed_length > std::numeric_limits::max()) { - LOG(ERROR) << "Entry " << name << " compressed size exceeds size of address space. " - << entry.compressed_length; - return false; - } - if (__builtin_add_overflow(pos, temp_entries[nextentry].second.compressed_length, &pos)) { - LOG(ERROR) << "`pos` overflows after adding " - << temp_entries[nextentry].second.compressed_length; - return false; - } - ++nextentry; - continue; - } - - // 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 = file_content_.size() - pos; - } - chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, raw_data_len); - - pos += raw_data_len; - } - - return true; -} - -bool ZipModeImage::AddZipEntryToChunks(ZipArchiveHandle handle, const std::string& entry_name, - ZipEntry64* entry) { - if (entry->compressed_length > std::numeric_limits::max()) { - LOG(ERROR) << "Failed to add " << entry_name - << " because's compressed size exceeds size of address space. " - << entry->compressed_length; - return false; - } - size_t compressed_len = entry->compressed_length; - if (compressed_len == 0) return true; - - // Split the entry into several normal chunks if it's too large. - if (limit_ > 0 && compressed_len > limit_) { - int count = 0; - while (compressed_len > 0) { - size_t length = std::min(limit_, compressed_len); - std::string name = entry_name + "-" + std::to_string(count); - chunks_.emplace_back(CHUNK_NORMAL, entry->offset + limit_ * count, &file_content_, length, - name); - - count++; - compressed_len -= length; - } - } else if (entry->method == kCompressDeflated) { - size_t uncompressed_len = entry->uncompressed_length; - if (uncompressed_len > std::numeric_limits::max()) { - LOG(ERROR) << "Failed to add " << entry_name - << " because's compressed size exceeds size of address space. " - << uncompressed_len; - return false; - } - std::vector uncompressed_data(uncompressed_len); - int ret = ExtractToMemory(handle, entry, uncompressed_data.data(), uncompressed_len); - if (ret != 0) { - LOG(ERROR) << "Failed to extract " << entry_name << " with size " << uncompressed_len << ": " - << ErrorCodeString(ret); - return false; - } - ImageChunk curr(CHUNK_DEFLATE, entry->offset, &file_content_, compressed_len, entry_name); - curr.SetUncompressedData(std::move(uncompressed_data)); - chunks_.push_back(std::move(curr)); - } else { - chunks_.emplace_back(CHUNK_NORMAL, entry->offset, &file_content_, compressed_len, entry_name); - } - - return true; -} - -// 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) { - LOG(ERROR) << "File is too small to be a zip file"; - return false; - } - - // 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(&file_content_[i]) == 0x06054b50) { - // double-check: this archive consists of a single "disk". - CHECK_EQ(get_unaligned(&file_content_[i + 4]), 0); - - uint16_t comment_length = get_unaligned(&file_content_[i + 20]); - size_t file_size = i + 22 + comment_length; - CHECK_LE(file_size, file_content_.size()); - *input_file_size = file_size; - return true; - } - } - } - - // EOCD not found, this file is likely not a valid zip file. - return false; -} - -ImageChunk ZipModeImage::PseudoSource() const { - CHECK(is_source_); - return ImageChunk(CHUNK_NORMAL, 0, &file_content_, file_content_.size()); -} - -const ImageChunk* ZipModeImage::FindChunkByName(const std::string& name, bool find_normal) const { - if (name.empty()) { - return nullptr; - } - for (auto& chunk : chunks_) { - if (chunk.GetType() != CHUNK_DEFLATE && !find_normal) { - continue; - } - - if (chunk.GetEntryName() == name) { - return &chunk; - } - - // Edge case when target chunk is split due to size limit but source chunk isn't. - if (name == (chunk.GetEntryName() + "-0") || chunk.GetEntryName() == (name + "-0")) { - return &chunk; - } - - // TODO handle the .so files with incremental version number. - // (e.g. lib/arm64-v8a/libcronet.59.0.3050.4.so) - } - - return nullptr; -} - -ImageChunk* ZipModeImage::FindChunkByName(const std::string& name, bool find_normal) { - return const_cast( - static_cast(this)->FindChunkByName(name, find_normal)); -} - -bool ZipModeImage::CheckAndProcessChunks(ZipModeImage* tgt_image, ZipModeImage* src_image) { - for (auto& tgt_chunk : *tgt_image) { - if (tgt_chunk.GetType() != CHUNK_DEFLATE) { - continue; - } - - ImageChunk* src_chunk = src_image->FindChunkByName(tgt_chunk.GetEntryName()); - if (src_chunk == nullptr) { - tgt_chunk.ChangeDeflateChunkToNormal(); - } else if (tgt_chunk == *src_chunk) { - // If two deflate chunks are identical (eg, the kernel has not changed between two builds), - // treat them as normal chunks. This makes applypatch much faster -- it can apply a trivial - // patch to the compressed data, rather than uncompressing and recompressing to apply the - // trivial patch to the uncompressed data. - tgt_chunk.ChangeDeflateChunkToNormal(); - src_chunk->ChangeDeflateChunkToNormal(); - } else if (!tgt_chunk.ReconstructDeflateChunk()) { - // We cannot recompress the data and get exactly the same bits as are in the input target - // image. Treat the chunk as a normal non-deflated chunk. - LOG(WARNING) << "Failed to reconstruct target deflate chunk [" << tgt_chunk.GetEntryName() - << "]; treating as normal"; - - tgt_chunk.ChangeDeflateChunkToNormal(); - src_chunk->ChangeDeflateChunkToNormal(); - } - } - - // For zips, we only need merge normal chunks for the target: deflated chunks are matched via - // filename, and normal chunks are patched using the entire source file as the source. - if (tgt_image->limit_ == 0) { - tgt_image->MergeAdjacentNormalChunks(); - tgt_image->DumpChunks(); - } - - return true; -} - -// For each target chunk, look for the corresponding source chunk by the zip_entry name. If -// found, add the range of this chunk in the original source file to the block aligned source -// ranges. Construct the split src & tgt image once the size of source range reaches limit. -bool ZipModeImage::SplitZipModeImageWithLimit(const ZipModeImage& tgt_image, - const ZipModeImage& src_image, - std::vector* split_tgt_images, - std::vector* split_src_images, - std::vector* split_src_ranges) { - CHECK_EQ(tgt_image.limit_, src_image.limit_); - size_t limit = tgt_image.limit_; - - src_image.DumpChunks(); - LOG(INFO) << "Splitting " << tgt_image.NumOfChunks() << " tgt chunks..."; - - SortedRangeSet used_src_ranges; // ranges used for previous split source images. - - // Reserve the central directory in advance for the last split image. - const auto& central_directory = src_image.cend() - 1; - CHECK_EQ(CHUNK_NORMAL, central_directory->GetType()); - used_src_ranges.Insert(central_directory->GetStartOffset(), - central_directory->DataLengthForPatch()); - - SortedRangeSet src_ranges; - std::vector split_src_chunks; - std::vector split_tgt_chunks; - for (auto tgt = tgt_image.cbegin(); tgt != tgt_image.cend(); tgt++) { - const ImageChunk* src = src_image.FindChunkByName(tgt->GetEntryName(), true); - if (src == nullptr) { - split_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt->GetStartOffset(), &tgt_image.file_content_, - tgt->GetRawDataLength()); - continue; - } - - size_t src_offset = src->GetStartOffset(); - size_t src_length = src->GetRawDataLength(); - - CHECK(src_length > 0); - CHECK_LE(src_length, limit); - - // Make sure this source range hasn't been used before so that the src_range pieces don't - // overlap with each other. - if (!RemoveUsedBlocks(&src_offset, &src_length, used_src_ranges)) { - split_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt->GetStartOffset(), &tgt_image.file_content_, - tgt->GetRawDataLength()); - } else if (src_ranges.blocks() * BLOCK_SIZE + src_length <= limit) { - src_ranges.Insert(src_offset, src_length); - - // Add the deflate source chunk if it hasn't been aligned. - if (src->GetType() == CHUNK_DEFLATE && src_length == src->GetRawDataLength()) { - split_src_chunks.push_back(*src); - split_tgt_chunks.push_back(*tgt); - } else { - // TODO split smarter to avoid alignment of large deflate chunks - split_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt->GetStartOffset(), &tgt_image.file_content_, - tgt->GetRawDataLength()); - } - } else { - bool added_image = ZipModeImage::AddSplitImageFromChunkList( - tgt_image, src_image, src_ranges, split_tgt_chunks, split_src_chunks, split_tgt_images, - split_src_images); - - split_tgt_chunks.clear(); - split_src_chunks.clear(); - // No need to update the split_src_ranges if we don't update the split source images. - if (added_image) { - used_src_ranges.Insert(src_ranges); - split_src_ranges->push_back(std::move(src_ranges)); - } - src_ranges = {}; - - // We don't have enough space for the current chunk; start a new split image and handle - // this chunk there. - tgt--; - } - } - - // TODO Trim it in case the CD exceeds limit too much. - src_ranges.Insert(central_directory->GetStartOffset(), central_directory->DataLengthForPatch()); - bool added_image = ZipModeImage::AddSplitImageFromChunkList(tgt_image, src_image, src_ranges, - split_tgt_chunks, split_src_chunks, - split_tgt_images, split_src_images); - if (added_image) { - split_src_ranges->push_back(std::move(src_ranges)); - } - - ValidateSplitImages(*split_tgt_images, *split_src_images, *split_src_ranges, - tgt_image.file_content_.size()); - - return true; -} - -bool ZipModeImage::AddSplitImageFromChunkList(const ZipModeImage& tgt_image, - const ZipModeImage& src_image, - const SortedRangeSet& split_src_ranges, - const std::vector& split_tgt_chunks, - const std::vector& split_src_chunks, - std::vector* split_tgt_images, - std::vector* split_src_images) { - CHECK(!split_tgt_chunks.empty()); - - std::vector aligned_tgt_chunks; - - // Align the target chunks in the beginning with BLOCK_SIZE. - size_t i = 0; - while (i < split_tgt_chunks.size()) { - size_t tgt_start = split_tgt_chunks[i].GetStartOffset(); - size_t tgt_length = split_tgt_chunks[i].GetRawDataLength(); - - // Current ImageChunk is long enough to align. - if (AlignHead(&tgt_start, &tgt_length)) { - aligned_tgt_chunks.emplace_back(CHUNK_NORMAL, tgt_start, &tgt_image.file_content_, - tgt_length); - break; - } - - i++; - } - - // Nothing left after alignment in the current split tgt chunks; skip adding the split_tgt_image. - if (i == split_tgt_chunks.size()) { - return false; - } - - aligned_tgt_chunks.insert(aligned_tgt_chunks.end(), split_tgt_chunks.begin() + i + 1, - split_tgt_chunks.end()); - CHECK(!aligned_tgt_chunks.empty()); - - // Add a normal chunk to align the contents in the end. - size_t end_offset = - aligned_tgt_chunks.back().GetStartOffset() + aligned_tgt_chunks.back().GetRawDataLength(); - if (end_offset % BLOCK_SIZE != 0 && end_offset < tgt_image.file_content_.size()) { - size_t tail_block_length = std::min(tgt_image.file_content_.size() - end_offset, - BLOCK_SIZE - (end_offset % BLOCK_SIZE)); - aligned_tgt_chunks.emplace_back(CHUNK_NORMAL, end_offset, &tgt_image.file_content_, - tail_block_length); - } - - ZipModeImage split_tgt_image(false); - split_tgt_image.Initialize(aligned_tgt_chunks, {}); - split_tgt_image.MergeAdjacentNormalChunks(); - - // Construct the split source file based on the split src ranges. - std::vector split_src_content; - for (const auto& r : split_src_ranges) { - size_t end = std::min(src_image.file_content_.size(), r.second * BLOCK_SIZE); - split_src_content.insert(split_src_content.end(), - src_image.file_content_.begin() + r.first * BLOCK_SIZE, - src_image.file_content_.begin() + end); - } - - // We should not have an empty src in our design; otherwise we will encounter an error in - // bsdiff since split_src_content.data() == nullptr. - CHECK(!split_src_content.empty()); - - ZipModeImage split_src_image(true); - split_src_image.Initialize(split_src_chunks, split_src_content); - - split_tgt_images->push_back(std::move(split_tgt_image)); - split_src_images->push_back(std::move(split_src_image)); - - return true; -} - -void ZipModeImage::ValidateSplitImages(const std::vector& split_tgt_images, - const std::vector& split_src_images, - std::vector& split_src_ranges, - size_t total_tgt_size) { - CHECK_EQ(split_tgt_images.size(), split_src_images.size()); - - LOG(INFO) << "Validating " << split_tgt_images.size() << " images"; - - // Verify that the target image pieces is continuous and can add up to the total size. - size_t last_offset = 0; - for (const auto& tgt_image : split_tgt_images) { - CHECK(!tgt_image.chunks_.empty()); - - CHECK_EQ(last_offset, tgt_image.chunks_.front().GetStartOffset()); - CHECK(last_offset % BLOCK_SIZE == 0); - - // Check the target chunks within the split image are continuous. - for (const auto& chunk : tgt_image.chunks_) { - CHECK_EQ(last_offset, chunk.GetStartOffset()); - last_offset += chunk.GetRawDataLength(); - } - } - CHECK_EQ(total_tgt_size, last_offset); - - // Verify that the source ranges are mutually exclusive. - CHECK_EQ(split_src_images.size(), split_src_ranges.size()); - SortedRangeSet used_src_ranges; - for (size_t i = 0; i < split_src_ranges.size(); i++) { - CHECK(!used_src_ranges.Overlaps(split_src_ranges[i])) - << "src range " << split_src_ranges[i].ToString() << " overlaps " - << used_src_ranges.ToString(); - used_src_ranges.Insert(split_src_ranges[i]); - } -} - -bool ZipModeImage::GeneratePatchesInternal(const ZipModeImage& tgt_image, - const ZipModeImage& src_image, - std::vector* patch_chunks) { - LOG(INFO) << "Constructing patches for " << tgt_image.NumOfChunks() << " chunks..."; - patch_chunks->clear(); - - bsdiff::SuffixArrayIndexInterface* bsdiff_cache = nullptr; - for (size_t i = 0; i < tgt_image.NumOfChunks(); i++) { - const auto& tgt_chunk = tgt_image[i]; - - if (PatchChunk::RawDataIsSmaller(tgt_chunk, 0)) { - patch_chunks->emplace_back(tgt_chunk); - continue; - } - - const ImageChunk* src_chunk = (tgt_chunk.GetType() != CHUNK_DEFLATE) - ? nullptr - : src_image.FindChunkByName(tgt_chunk.GetEntryName()); - - const auto& src_ref = (src_chunk == nullptr) ? src_image.PseudoSource() : *src_chunk; - bsdiff::SuffixArrayIndexInterface** bsdiff_cache_ptr = - (src_chunk == nullptr) ? &bsdiff_cache : nullptr; - - std::vector patch_data; - if (!ImageChunk::MakePatch(tgt_chunk, src_ref, &patch_data, bsdiff_cache_ptr)) { - LOG(ERROR) << "Failed to generate patch, name: " << tgt_chunk.GetEntryName(); - return false; - } - - LOG(INFO) << "patch " << i << " is " << patch_data.size() << " bytes (of " - << tgt_chunk.GetRawDataLength() << ")"; - - if (PatchChunk::RawDataIsSmaller(tgt_chunk, patch_data.size())) { - patch_chunks->emplace_back(tgt_chunk); - } else { - patch_chunks->emplace_back(tgt_chunk, src_ref, std::move(patch_data)); - } - } - delete bsdiff_cache; - - CHECK_EQ(patch_chunks->size(), tgt_image.NumOfChunks()); - return true; -} - -bool ZipModeImage::GeneratePatches(const ZipModeImage& tgt_image, const ZipModeImage& src_image, - const std::string& patch_name) { - std::vector patch_chunks; - - ZipModeImage::GeneratePatchesInternal(tgt_image, src_image, &patch_chunks); - - CHECK_EQ(tgt_image.NumOfChunks(), patch_chunks.size()); - - android::base::unique_fd patch_fd( - open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR)); - if (patch_fd == -1) { - PLOG(ERROR) << "Failed to open " << patch_name; - return false; - } - - return PatchChunk::WritePatchDataToFd(patch_chunks, patch_fd); -} - -bool ZipModeImage::GeneratePatches(const std::vector& split_tgt_images, - const std::vector& split_src_images, - const std::vector& split_src_ranges, - const std::string& patch_name, - const std::string& split_info_file, - const std::string& debug_dir) { - LOG(INFO) << "Constructing patches for " << split_tgt_images.size() << " split images..."; - - 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) { - PLOG(ERROR) << "Failed to open " << patch_name; - return false; - } - - std::vector split_info_list; - for (size_t i = 0; i < split_tgt_images.size(); i++) { - std::vector patch_chunks; - if (!ZipModeImage::GeneratePatchesInternal(split_tgt_images[i], split_src_images[i], - &patch_chunks)) { - LOG(ERROR) << "Failed to generate split patch"; - return false; - } - - size_t total_patch_size = 12; - for (auto& p : patch_chunks) { - p.UpdateSourceOffset(split_src_ranges[i]); - total_patch_size += p.PatchSize(); - } - - if (!PatchChunk::WritePatchDataToFd(patch_chunks, patch_fd)) { - return false; - } - - size_t split_tgt_size = split_tgt_images[i].chunks_.back().GetStartOffset() + - split_tgt_images[i].chunks_.back().GetRawDataLength() - - split_tgt_images[i].chunks_.front().GetStartOffset(); - std::string split_info = android::base::StringPrintf( - "%zu %zu %s", total_patch_size, split_tgt_size, split_src_ranges[i].ToString().c_str()); - split_info_list.push_back(split_info); - - // Write the split source & patch into the debug directory. - if (!debug_dir.empty()) { - std::string src_name = android::base::StringPrintf("%s/src-%zu", debug_dir.c_str(), i); - android::base::unique_fd fd( - open(src_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR)); - - if (fd == -1) { - PLOG(ERROR) << "Failed to open " << src_name; - return false; - } - if (!android::base::WriteFully(fd, split_src_images[i].PseudoSource().DataForPatch(), - split_src_images[i].PseudoSource().DataLengthForPatch())) { - PLOG(ERROR) << "Failed to write split source data into " << src_name; - return false; - } - - std::string patch_name = android::base::StringPrintf("%s/patch-%zu", debug_dir.c_str(), i); - fd.reset(open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR)); - - if (fd == -1) { - PLOG(ERROR) << "Failed to open " << patch_name; - return false; - } - if (!PatchChunk::WritePatchDataToFd(patch_chunks, fd)) { - return false; - } - } - } - - // Store the split in the following format: - // Line 0: imgdiff version# - // Line 1: number of pieces - // Line 2: patch_size_1 tgt_size_1 src_range_1 - // ... - // Line n+1: patch_size_n tgt_size_n src_range_n - std::string split_info_string = android::base::StringPrintf( - "%zu\n%zu\n", VERSION, split_info_list.size()) + android::base::Join(split_info_list, '\n'); - if (!android::base::WriteStringToFile(split_info_string, split_info_file)) { - PLOG(ERROR) << "Failed to write split info to " << split_info_file; - return false; - } - - return true; -} - -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(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, &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, &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 - // the uncompressed_data of the image body and its length. - - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = sz - pos; - strm.next_in = file_content_.data() + pos; - - // -15 means we are decoding a 'raw' deflate stream; zlib will - // not expect zlib headers. - int ret = inflateInit2(&strm, -15); - if (ret < 0) { - LOG(ERROR) << "Failed to initialize inflate: " << ret; - return false; - } - - size_t allocated = BUFFER_SIZE; - std::vector uncompressed_data(allocated); - size_t uncompressed_len = 0, raw_data_len = 0; - do { - strm.avail_out = allocated - uncompressed_len; - strm.next_out = uncompressed_data.data() + uncompressed_len; - ret = inflate(&strm, Z_NO_FLUSH); - if (ret < 0) { - LOG(WARNING) << "Inflate failed [" << strm.msg << "] at offset [" << chunk_offset - << "]; treating as a normal chunk"; - break; - } - uncompressed_len = allocated - strm.avail_out; - if (strm.avail_out == 0) { - allocated *= 2; - uncompressed_data.resize(allocated); - } - } while (ret != Z_STREAM_END); - - raw_data_len = sz - strm.avail_in - pos; - inflateEnd(&strm); - - if (ret < 0) { - continue; - } - - // 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) { - LOG(WARNING) << "invalid footer position; treating as a normal chunk"; - continue; - } - size_t footer_size = get_unaligned(file_content_.data() + footer_index); - if (footer_size != uncompressed_len) { - LOG(WARNING) << "footer size " << footer_size << " != " << uncompressed_len - << "; treating as a normal chunk"; - 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(std::move(body)); - - pos += raw_data_len; - - // create a normal chunk for the footer - chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, GZIP_FOOTER_LEN); - - pos += GZIP_FOOTER_LEN; - } 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). - - // Scan forward until we find a gzip header. - size_t data_len = 0; - while (data_len + pos < sz) { - if (data_len + pos + 4 <= sz && - get_unaligned(file_content_.data() + pos + data_len) == 0x00088b1f) { - break; - } - data_len++; - } - chunks_.emplace_back(CHUNK_NORMAL, pos, &file_content_, data_len); - - pos += data_len; - } - } - - return true; -} - -bool ImageModeImage::SetBonusData(const std::vector& bonus_data) { - CHECK(is_source_); - if (chunks_.size() < 2 || !chunks_[1].SetBonusData(bonus_data)) { - LOG(ERROR) << "Failed to set bonus data"; - DumpChunks(); - return false; - } - - LOG(INFO) << " using " << bonus_data.size() << " bytes of bonus data"; - return true; -} - -// 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()) { - LOG(ERROR) << "Source and target don't have same number of chunks!"; - tgt_image->DumpChunks(); - src_image->DumpChunks(); - return false; - } - for (size_t i = 0; i < tgt_image->NumOfChunks(); ++i) { - if ((*tgt_image)[i].GetType() != (*src_image)[i].GetType()) { - LOG(ERROR) << "Source and target don't have same chunk structure! (chunk " << i << ")"; - tgt_image->DumpChunks(); - src_image->DumpChunks(); - return false; - } - } - - for (size_t i = 0; i < tgt_image->NumOfChunks(); ++i) { - auto& tgt_chunk = (*tgt_image)[i]; - auto& src_chunk = (*src_image)[i]; - if (tgt_chunk.GetType() != CHUNK_DEFLATE) { - continue; - } - - // If two deflate chunks are identical treat them as normal chunks. - if (tgt_chunk == src_chunk) { - tgt_chunk.ChangeDeflateChunkToNormal(); - src_chunk.ChangeDeflateChunkToNormal(); - } else if (!tgt_chunk.ReconstructDeflateChunk()) { - // We cannot recompress the data and get exactly the same bits as are in the input target - // image, fall back to normal - LOG(WARNING) << "Failed to reconstruct target deflate chunk " << i << " [" - << tgt_chunk.GetEntryName() << "]; treating as normal"; - tgt_chunk.ChangeDeflateChunkToNormal(); - src_chunk.ChangeDeflateChunkToNormal(); - } - } - - // 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. - LOG(ERROR) << "Merging normal chunks went awry"; - return false; - } - - return true; -} - -// In image mode, generate patches against the given source chunks and bonus_data; write the -// result to |patch_name|. -bool ImageModeImage::GeneratePatches(const ImageModeImage& tgt_image, - const ImageModeImage& src_image, - const std::string& patch_name) { - LOG(INFO) << "Constructing patches for " << tgt_image.NumOfChunks() << " chunks..."; - std::vector patch_chunks; - patch_chunks.reserve(tgt_image.NumOfChunks()); - - for (size_t i = 0; i < tgt_image.NumOfChunks(); i++) { - const auto& tgt_chunk = tgt_image[i]; - const auto& src_chunk = src_image[i]; - - if (PatchChunk::RawDataIsSmaller(tgt_chunk, 0)) { - patch_chunks.emplace_back(tgt_chunk); - continue; - } - - std::vector patch_data; - if (!ImageChunk::MakePatch(tgt_chunk, src_chunk, &patch_data, nullptr)) { - LOG(ERROR) << "Failed to generate patch for target chunk " << i; - return false; - } - LOG(INFO) << "patch " << i << " is " << patch_data.size() << " bytes (of " - << tgt_chunk.GetRawDataLength() << ")"; - - if (PatchChunk::RawDataIsSmaller(tgt_chunk, patch_data.size())) { - patch_chunks.emplace_back(tgt_chunk); - } else { - patch_chunks.emplace_back(tgt_chunk, src_chunk, std::move(patch_data)); - } - } - - CHECK_EQ(tgt_image.NumOfChunks(), patch_chunks.size()); - - android::base::unique_fd patch_fd( - open(patch_name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR)); - if (patch_fd == -1) { - PLOG(ERROR) << "Failed to open " << patch_name; - return false; - } - - return PatchChunk::WritePatchDataToFd(patch_chunks, patch_fd); -} - -int imgdiff(int argc, const char** argv) { - bool verbose = false; - bool zip_mode = false; - std::vector bonus_data; - size_t blocks_limit = 0; - std::string split_info_file; - std::string debug_dir; - - int opt; - int option_index; - optind = 0; // Reset the getopt state so that we can call it multiple times for test. - - while ((opt = getopt_long(argc, const_cast(argv), "zb:v", OPTIONS, &option_index)) != - -1) { - switch (opt) { - case 'z': - zip_mode = true; - break; - case 'b': { - android::base::unique_fd fd(open(optarg, O_RDONLY)); - if (fd == -1) { - PLOG(ERROR) << "Failed to open bonus file " << optarg; - return 1; - } - struct stat st; - if (fstat(fd, &st) != 0) { - PLOG(ERROR) << "Failed to stat bonus file " << optarg; - return 1; - } - - size_t bonus_size = st.st_size; - bonus_data.resize(bonus_size); - if (!android::base::ReadFully(fd, bonus_data.data(), bonus_size)) { - PLOG(ERROR) << "Failed to read bonus file " << optarg; - return 1; - } - break; - } - case 'v': - verbose = true; - break; - case 0: { - std::string name = OPTIONS[option_index].name; - if (name == "block-limit" && !android::base::ParseUint(optarg, &blocks_limit)) { - LOG(ERROR) << "Failed to parse size blocks_limit: " << optarg; - return 1; - } else if (name == "split-info") { - split_info_file = optarg; - } else if (name == "debug-dir") { - debug_dir = optarg; - } - break; - } - default: - LOG(ERROR) << "unexpected opt: " << static_cast(opt); - return 2; - } - } - - if (!verbose) { - android::base::SetMinimumLogSeverity(android::base::WARNING); - } - - if (argc - optind != 3) { - LOG(ERROR) << "usage: " << argv[0] << " [options] "; - LOG(ERROR) - << " -z , Generate patches in zip mode, src and tgt should be zip files.\n" - " -b , Bonus file in addition to src, image mode only.\n" - " --block-limit, For large zips, split the src and tgt based on the block limit;\n" - " and generate patches between each pair of pieces. Concatenate " - "these\n" - " patches together and output them into .\n" - " --split-info, Output the split information (patch_size, tgt_size, src_ranges);\n" - " zip mode with block-limit only.\n" - " --debug-dir, Debug directory to put the split srcs and patches, zip mode only.\n" - " -v, --verbose, Enable verbose logging."; - return 2; - } - - if (zip_mode) { - ZipModeImage src_image(true, blocks_limit * BLOCK_SIZE); - ZipModeImage tgt_image(false, blocks_limit * BLOCK_SIZE); - - if (!src_image.Initialize(argv[optind])) { - return 1; - } - if (!tgt_image.Initialize(argv[optind + 1])) { - return 1; - } - - if (!ZipModeImage::CheckAndProcessChunks(&tgt_image, &src_image)) { - return 1; - } - - // Compute bsdiff patches for each chunk's data (the uncompressed data, in the case of - // deflate chunks). - if (blocks_limit > 0) { - if (split_info_file.empty()) { - LOG(ERROR) << "split-info path cannot be empty when generating patches with a block-limit"; - return 1; - } - - std::vector split_tgt_images; - std::vector split_src_images; - std::vector split_src_ranges; - ZipModeImage::SplitZipModeImageWithLimit(tgt_image, src_image, &split_tgt_images, - &split_src_images, &split_src_ranges); - - if (!ZipModeImage::GeneratePatches(split_tgt_images, split_src_images, split_src_ranges, - argv[optind + 2], split_info_file, debug_dir)) { - return 1; - } - - } else if (!ZipModeImage::GeneratePatches(tgt_image, src_image, argv[optind + 2])) { - return 1; - } - } else { - ImageModeImage src_image(true); - ImageModeImage tgt_image(false); - - if (!src_image.Initialize(argv[optind])) { - return 1; - } - if (!tgt_image.Initialize(argv[optind + 1])) { - return 1; - } - - if (!ImageModeImage::CheckAndProcessChunks(&tgt_image, &src_image)) { - return 1; - } - - if (!bonus_data.empty() && !src_image.SetBonusData(bonus_data)) { - return 1; - } - - if (!ImageModeImage::GeneratePatches(tgt_image, src_image, argv[optind + 2])) { - return 1; - } - } - - return 0; -} diff --git a/applypatch/imgdiff_main.cpp b/applypatch/imgdiff_main.cpp deleted file mode 100644 index 7d5bdf9a..00000000 --- a/applypatch/imgdiff_main.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "applypatch/imgdiff.h" - -int main(int argc, char** argv) { - return imgdiff(argc, const_cast(argv)); -} diff --git a/applypatch/imgpatch.cpp b/applypatch/imgpatch.cpp deleted file mode 100644 index f4c33e5a..00000000 --- a/applypatch/imgpatch.cpp +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// See imgdiff.cpp in this directory for a description of the patch file -// format. - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "edify/expr.h" -#include "otautil/print_sha1.h" - -static inline int64_t Read8(const void *address) { - return android::base::get_unaligned(address); -} - -static inline int32_t Read4(const void *address) { - return android::base::get_unaligned(address); -} - -// This function is a wrapper of ApplyBSDiffPatch(). It has a custom sink function to deflate the -// patched data and stream the deflated data to output. -static bool ApplyBSDiffPatchAndStreamOutput(const uint8_t* src_data, size_t src_len, - const Value& patch, size_t patch_offset, - const char* deflate_header, SinkFn sink) { - size_t expected_target_length = static_cast(Read8(deflate_header + 32)); - CHECK_GT(expected_target_length, static_cast(0)); - int level = Read4(deflate_header + 40); - int method = Read4(deflate_header + 44); - int window_bits = Read4(deflate_header + 48); - int mem_level = Read4(deflate_header + 52); - int strategy = Read4(deflate_header + 56); - - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = 0; - strm.next_in = nullptr; - int ret = deflateInit2(&strm, level, method, window_bits, mem_level, strategy); - if (ret != Z_OK) { - LOG(ERROR) << "Failed to init uncompressed data deflation: " << ret; - return false; - } - - // Define a custom sink wrapper that feeds to bspatch. It deflates the available patch data on - // the fly and outputs the compressed data to the given sink. - size_t actual_target_length = 0; - size_t total_written = 0; - static constexpr size_t buffer_size = 32768; - auto compression_sink = [&strm, &actual_target_length, &expected_target_length, &total_written, - &ret, &sink](const uint8_t* data, size_t len) -> size_t { - // The input patch length for an update never exceeds INT_MAX. - strm.avail_in = len; - strm.next_in = data; - do { - std::vector buffer(buffer_size); - strm.avail_out = buffer_size; - strm.next_out = buffer.data(); - if (actual_target_length + len < expected_target_length) { - ret = deflate(&strm, Z_NO_FLUSH); - } else { - ret = deflate(&strm, Z_FINISH); - } - if (ret != Z_OK && ret != Z_STREAM_END) { - LOG(ERROR) << "Failed to deflate stream: " << ret; - // zero length indicates an error in the sink function of bspatch(). - return 0; - } - - size_t have = buffer_size - strm.avail_out; - total_written += have; - if (sink(buffer.data(), have) != have) { - LOG(ERROR) << "Failed to write " << have << " compressed bytes to output."; - return 0; - } - } while ((strm.avail_in != 0 || strm.avail_out == 0) && ret != Z_STREAM_END); - - actual_target_length += len; - return len; - }; - - int bspatch_result = ApplyBSDiffPatch(src_data, src_len, patch, patch_offset, compression_sink); - deflateEnd(&strm); - - if (bspatch_result != 0) { - return false; - } - - if (ret != Z_STREAM_END) { - LOG(ERROR) << "ret is expected to be Z_STREAM_END, but it's " << ret; - return false; - } - - if (expected_target_length != actual_target_length) { - LOG(ERROR) << "target length is expected to be " << expected_target_length << ", but it's " - << actual_target_length; - return false; - } - LOG(DEBUG) << "bspatch wrote " << total_written << " bytes in total to streaming output."; - - return true; -} - -int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data, - size_t patch_size, SinkFn sink) { - Value patch(Value::Type::BLOB, - std::string(reinterpret_cast(patch_data), patch_size)); - return ApplyImagePatch(old_data, old_size, patch, sink, nullptr); -} - -int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink, - const Value* bonus_data) { - if (patch.data.size() < 12) { - printf("patch too short to contain header\n"); - return -1; - } - - // IMGDIFF2 uses CHUNK_NORMAL, CHUNK_DEFLATE, and CHUNK_RAW. (IMGDIFF1, which is no longer - // supported, used CHUNK_NORMAL and CHUNK_GZIP.) - const char* const patch_header = patch.data.data(); - if (memcmp(patch_header, "IMGDIFF2", 8) != 0) { - printf("corrupt patch file header (magic number)\n"); - return -1; - } - - int num_chunks = Read4(patch_header + 8); - size_t pos = 12; - for (int i = 0; i < num_chunks; ++i) { - // each chunk's header record starts with 4 bytes. - if (pos + 4 > patch.data.size()) { - printf("failed to read chunk %d record\n", i); - return -1; - } - int type = Read4(patch_header + pos); - pos += 4; - - if (type == CHUNK_NORMAL) { - const char* normal_header = patch_header + pos; - pos += 24; - if (pos > patch.data.size()) { - printf("failed to read chunk %d normal header data\n", i); - return -1; - } - - size_t src_start = static_cast(Read8(normal_header)); - size_t src_len = static_cast(Read8(normal_header + 8)); - size_t patch_offset = static_cast(Read8(normal_header + 16)); - - if (src_start + src_len > old_size) { - printf("source data too short\n"); - return -1; - } - if (ApplyBSDiffPatch(old_data + src_start, src_len, patch, patch_offset, sink) != 0) { - printf("Failed to apply bsdiff patch.\n"); - return -1; - } - - LOG(DEBUG) << "Processed chunk type normal"; - } else if (type == CHUNK_RAW) { - const char* raw_header = patch_header + pos; - pos += 4; - if (pos > patch.data.size()) { - printf("failed to read chunk %d raw header data\n", i); - return -1; - } - - size_t data_len = static_cast(Read4(raw_header)); - - if (pos + data_len > patch.data.size()) { - printf("failed to read chunk %d raw data\n", i); - return -1; - } - if (sink(reinterpret_cast(patch_header + pos), data_len) != data_len) { - printf("failed to write chunk %d raw data\n", i); - return -1; - } - pos += data_len; - - LOG(DEBUG) << "Processed chunk type raw"; - } else if (type == CHUNK_DEFLATE) { - // deflate chunks have an additional 60 bytes in their chunk header. - const char* deflate_header = patch_header + pos; - pos += 60; - if (pos > patch.data.size()) { - printf("failed to read chunk %d deflate header data\n", i); - return -1; - } - - size_t src_start = static_cast(Read8(deflate_header)); - size_t src_len = static_cast(Read8(deflate_header + 8)); - size_t patch_offset = static_cast(Read8(deflate_header + 16)); - size_t expanded_len = static_cast(Read8(deflate_header + 24)); - - if (src_start + src_len > old_size) { - printf("source data too short\n"); - return -1; - } - - // Decompress the source data; the chunk header tells us exactly - // how big we expect it to be when decompressed. - - // Note: expanded_len will include the bonus data size if the patch was constructed with - // bonus data. The deflation will come up 'bonus_size' bytes short; these must be appended - // from the bonus_data value. - size_t bonus_size = (i == 1 && bonus_data != nullptr) ? bonus_data->data.size() : 0; - - std::vector expanded_source(expanded_len); - - // inflate() doesn't like strm.next_out being a nullptr even with - // avail_out being zero (Z_STREAM_ERROR). - if (expanded_len != 0) { - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = src_len; - strm.next_in = old_data + src_start; - strm.avail_out = expanded_len; - strm.next_out = expanded_source.data(); - - int ret = inflateInit2(&strm, -15); - if (ret != Z_OK) { - printf("failed to init source inflation: %d\n", ret); - return -1; - } - - // Because we've provided enough room to accommodate the output - // data, we expect one call to inflate() to suffice. - ret = inflate(&strm, Z_SYNC_FLUSH); - if (ret != Z_STREAM_END) { - printf("source inflation returned %d\n", ret); - return -1; - } - // We should have filled the output buffer exactly, except - // for the bonus_size. - if (strm.avail_out != bonus_size) { - printf("source inflation short by %zu bytes\n", strm.avail_out - bonus_size); - return -1; - } - inflateEnd(&strm); - - if (bonus_size) { - memcpy(expanded_source.data() + (expanded_len - bonus_size), bonus_data->data.data(), - bonus_size); - } - } - - if (!ApplyBSDiffPatchAndStreamOutput(expanded_source.data(), expanded_len, patch, - patch_offset, deflate_header, sink)) { - LOG(ERROR) << "Fail to apply streaming bspatch."; - return -1; - } - - LOG(DEBUG) << "Processed chunk type deflate"; - } else { - printf("patch chunk %d is unknown type %d\n", i, type); - return -1; - } - } - - return 0; -} diff --git a/applypatch/include/applypatch/applypatch.h b/applypatch/include/applypatch/applypatch.h deleted file mode 100644 index 799f4b2d..00000000 --- a/applypatch/include/applypatch/applypatch.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _APPLYPATCH_H -#define _APPLYPATCH_H - -#include - -#include -#include -#include -#include -#include - -#include - -// Forward declaration to avoid including "edify/expr.h" in the header. -struct Value; - -struct FileContents { - uint8_t sha1[SHA_DIGEST_LENGTH]; - std::vector data; -}; - -using SinkFn = std::function; - -// applypatch.cpp - -int ShowLicenses(); - -// Parses a given string of 40 hex digits into 20-byte array 'digest'. 'str' may contain only the -// digest or be of the form ":". Returns 0 on success, or -1 on any error. -int ParseSha1(const std::string& str, uint8_t* digest); - -struct Partition { - Partition() = default; - - Partition(const std::string& name, size_t size, const std::string& hash) - : name(name), size(size), hash(hash) {} - - // Parses and returns the given string into a Partition object. The input string is of the form - // "EMMC:::". Returns the parsed Partition, or an empty object on error. - static Partition Parse(const std::string& partition, std::string* err); - - std::string ToString() const; - - // Returns whether the current Partition object is valid. - explicit operator bool() const { - return !name.empty(); - } - - std::string name; - size_t size; - std::string hash; -}; - -std::ostream& operator<<(std::ostream& os, const Partition& partition); - -// Applies the given 'patch' to the 'source' Partition, verifies then writes the patching result to -// the 'target' Partition. While patching, it will backup the data on the source partition to -// /cache, so that the patching could be resumed on interruption even if both of the source and -// target partitions refer to the same device. The function is idempotent if called multiple times. -// 'bonus' can be provided if the patch was generated with a bonus output, or nullptr. -// 'backup_source' indicates whether the source partition should be backed up prior to the update -// (e.g. when doing in-place update). Returns the patching result. -bool PatchPartition(const Partition& target, const Partition& source, const Value& patch, - const Value* bonus, bool backup_source); - -// Returns whether the contents of the eMMC target or the cached file match the embedded hash. -// It will look for the backup on /cache if the given partition doesn't match the checksum. -bool PatchPartitionCheck(const Partition& target, const Partition& source); - -// Checks whether the contents of the given partition has the desired hash. It will NOT look for -// the backup on /cache if the given partition doesn't have the expected checksum. -bool CheckPartition(const Partition& target); - -// Flashes a given image in 'source_filename' to the eMMC target partition. It verifies the target -// checksum first, and will return if target already has the desired hash. Otherwise it checks the -// checksum of the given source image, flashes, and verifies the target partition afterwards. The -// function is idempotent. Returns the flashing result. -bool FlashPartition(const Partition& target, const std::string& source_filename); - -// Reads a file into memory; stores the file contents and associated metadata in *file. -bool LoadFileContents(const std::string& filename, FileContents* file); - -// Saves the given FileContents object to the given filename. -bool SaveFileContents(const std::string& filename, const FileContents* file); - -// bspatch.cpp - -void ShowBSDiffLicense(); - -// Applies the bsdiff-patch given in 'patch' (from offset 'patch_offset' to the end) to the source -// data given by (old_data, old_size). Writes the patched output through the given 'sink'. Returns -// 0 on success. -int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value& patch, - size_t patch_offset, SinkFn sink); - -// imgpatch.cpp - -// Applies the imgdiff-patch given in 'patch' to the source data given by (old_data, old_size), with -// the optional bonus data. Writes the patched output through the given 'sink'. Returns 0 on -// success. -int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink, - const Value* bonus_data); - -// freecache.cpp - -// Checks whether /cache partition has at least 'bytes'-byte free space. Returns true immediately -// if so. Otherwise, it will try to free some space by removing older logs, checks again and -// returns the checking result. -bool CheckAndFreeSpaceOnCache(size_t bytes); - -// Removes the files in |dirname| until we have at least |bytes_needed| bytes of free space on the -// partition. |space_checker| should return the size of the free space, or -1 on error. -bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname, - const std::function& space_checker); -#endif diff --git a/applypatch/include/applypatch/imgdiff.h b/applypatch/include/applypatch/imgdiff.h deleted file mode 100644 index 22cbd4fa..00000000 --- a/applypatch/include/applypatch/imgdiff.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _APPLYPATCH_IMGDIFF_H -#define _APPLYPATCH_IMGDIFF_H - -#include - -// Image patch chunk types -#define CHUNK_NORMAL 0 -#define CHUNK_GZIP 1 // version 1 only -#define CHUNK_DEFLATE 2 // version 2 only -#define CHUNK_RAW 3 // version 2 only - -// The gzip header size is actually variable, but we currently don't -// support gzipped data with any of the optional fields, so for now it -// will always be ten bytes. See RFC 1952 for the definition of the -// gzip format. -static constexpr size_t GZIP_HEADER_LEN = 10; - -// The gzip footer size really is fixed. -static constexpr size_t GZIP_FOOTER_LEN = 8; - -int imgdiff(int argc, const char** argv); - -#endif // _APPLYPATCH_IMGDIFF_H diff --git a/applypatch/include/applypatch/imgdiff_image.h b/applypatch/include/applypatch/imgdiff_image.h deleted file mode 100644 index b579e56a..00000000 --- a/applypatch/include/applypatch/imgdiff_image.h +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _APPLYPATCH_IMGDIFF_IMAGE_H -#define _APPLYPATCH_IMGDIFF_IMAGE_H - -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include "imgdiff.h" -#include "otautil/rangeset.h" - -class ImageChunk { - public: - static constexpr auto WINDOWBITS = -15; // 32kb window; negative to indicate a raw stream. - static constexpr auto MEMLEVEL = 8; // the default value. - static constexpr auto METHOD = Z_DEFLATED; - static constexpr auto STRATEGY = Z_DEFAULT_STRATEGY; - - ImageChunk(int type, size_t start, const std::vector* file_content, size_t raw_data_len, - std::string entry_name = {}); - - int GetType() const { - return type_; - } - - const uint8_t* GetRawData() const; - size_t GetRawDataLength() const { - return raw_data_len_; - } - const std::string& GetEntryName() const { - return entry_name_; - } - size_t GetStartOffset() const { - return start_; - } - int GetCompressLevel() const { - return compress_level_; - } - - // CHUNK_DEFLATE will return the uncompressed data for diff, while other types will simply return - // the raw data. - const uint8_t* DataForPatch() const; - size_t DataLengthForPatch() const; - - void Dump(size_t index) const; - - void SetUncompressedData(std::vector data); - bool SetBonusData(const std::vector& bonus_data); - - bool operator==(const ImageChunk& other) const; - bool operator!=(const ImageChunk& other) const { - return !(*this == other); - } - - /* - * Cause a gzip chunk to be treated as a normal chunk (ie, as a blob of uninterpreted data). - * The resulting patch will likely be about as big as the target file, but it lets us handle - * the case of images where some gzip chunks are reconstructible but others aren't (by treating - * the ones that aren't as normal chunks). - */ - void ChangeDeflateChunkToNormal(); - - /* - * Verify that we can reproduce exactly the same compressed data that we started with. Sets the - * level, method, windowBits, memLevel, and strategy fields in the chunk to the encoding - * parameters needed to produce the right output. - */ - bool ReconstructDeflateChunk(); - bool IsAdjacentNormal(const ImageChunk& other) const; - void MergeAdjacentNormal(const ImageChunk& other); - - /* - * Compute a bsdiff patch between |src| and |tgt|; Store the result in the patch_data. - * |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk is used - * repeatedly, pass nullptr if not needed. - */ - static bool MakePatch(const ImageChunk& tgt, const ImageChunk& src, - std::vector* patch_data, - bsdiff::SuffixArrayIndexInterface** bsdiff_cache); - - private: - bool TryReconstruction(int level); - - int type_; // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW - size_t start_; // offset of chunk in the original input file - const std::vector* input_file_ptr_; // ptr to the full content of original input file - size_t raw_data_len_; - - // deflate encoder parameters - int compress_level_; - - // --- for CHUNK_DEFLATE chunks only: --- - std::vector uncompressed_data_; - std::string entry_name_; // used for zip entries -}; - -// PatchChunk stores the patch data between a source chunk and a target chunk. It also keeps track -// of the metadata of src&tgt chunks (e.g. offset, raw data length, uncompressed data length). -class PatchChunk { - public: - PatchChunk(const ImageChunk& tgt, const ImageChunk& src, std::vector data); - - // Construct a CHUNK_RAW patch from the target data directly. - explicit PatchChunk(const ImageChunk& tgt); - - // Return true if raw data size is smaller than the patch size. - static bool RawDataIsSmaller(const ImageChunk& tgt, size_t patch_size); - - // Update the source start with the new offset within the source range. - void UpdateSourceOffset(const SortedRangeSet& src_range); - - // Return the total size (header + data) of the patch. - size_t PatchSize() const; - - static bool WritePatchDataToFd(const std::vector& patch_chunks, int patch_fd); - - private: - size_t GetHeaderSize() const; - size_t WriteHeaderToFd(int fd, size_t offset, size_t index) const; - - // The patch chunk type is the same as the target chunk type. The only exception is we change - // the |type_| to CHUNK_RAW if target length is smaller than the patch size. - int type_; - - size_t source_start_; - size_t source_len_; - size_t source_uncompressed_len_; - - size_t target_start_; // offset of the target chunk within the target file - size_t target_len_; - size_t target_uncompressed_len_; - size_t target_compress_level_; // the deflate compression level of the target chunk. - - std::vector data_; // storage for the patch data -}; - -// Interface for zip_mode and image_mode images. We initialize the image from an input file and -// split the file content into a list of image chunks. -class Image { - public: - explicit Image(bool is_source) : is_source_(is_source) {} - - virtual ~Image() {} - - // Create a list of image chunks from input file. - virtual bool Initialize(const std::string& filename) = 0; - - // Look for runs of adjacent normal chunks and compress them down into a single chunk. (Such - // runs can be produced when deflate chunks are changed to normal chunks.) - void MergeAdjacentNormalChunks(); - - void DumpChunks() const; - - // Non const iterators to access the stored ImageChunks. - std::vector::iterator begin() { - return chunks_.begin(); - } - - std::vector::iterator end() { - return chunks_.end(); - } - - std::vector::const_iterator cbegin() const { - return chunks_.cbegin(); - } - - std::vector::const_iterator cend() const { - return chunks_.cend(); - } - - ImageChunk& operator[](size_t i); - const ImageChunk& operator[](size_t i) const; - - size_t NumOfChunks() const { - return chunks_.size(); - } - - protected: - bool ReadFile(const std::string& filename, std::vector* file_content); - - bool is_source_; // True if it's for source chunks. - std::vector chunks_; // Internal storage of ImageChunk. - std::vector file_content_; // Store the whole input file in memory. -}; - -class ZipModeImage : public Image { - public: - explicit ZipModeImage(bool is_source, size_t limit = 0) : Image(is_source), limit_(limit) {} - - bool Initialize(const std::string& filename) override; - - // Initialize a fake ZipModeImage from an existing ImageChunk vector. For src img pieces, we - // reconstruct a new file_content based on the source ranges; but it's not needed for the tgt img - // pieces; because for each chunk both the data and their offset within the file are unchanged. - void Initialize(const std::vector& chunks, const std::vector& file_content) { - chunks_ = chunks; - file_content_ = file_content; - } - - // The pesudo source chunk for bsdiff if there's no match for the given target chunk. It's in - // fact the whole source file. - ImageChunk PseudoSource() const; - - // Find the matching deflate source chunk by entry name. Search for normal chunks also if - // |find_normal| is true. - ImageChunk* FindChunkByName(const std::string& name, bool find_normal = false); - - const ImageChunk* FindChunkByName(const std::string& name, bool find_normal = false) const; - - // Verify that we can reconstruct the deflate chunks; also change the type to CHUNK_NORMAL if - // src and tgt are identical. - static bool CheckAndProcessChunks(ZipModeImage* tgt_image, ZipModeImage* src_image); - - // Compute the patch between tgt & src images, and write the data into |patch_name|. - static bool GeneratePatches(const ZipModeImage& tgt_image, const ZipModeImage& src_image, - const std::string& patch_name); - - // Compute the patch based on the lists of split src and tgt images. Generate patches for each - // pair of split pieces and write the data to |patch_name|. If |debug_dir| is specified, write - // each split src data and patch data into that directory. - static bool GeneratePatches(const std::vector& split_tgt_images, - const std::vector& split_src_images, - const std::vector& split_src_ranges, - const std::string& patch_name, const std::string& split_info_file, - const std::string& debug_dir); - - // Split the tgt chunks and src chunks based on the size limit. - static bool SplitZipModeImageWithLimit(const ZipModeImage& tgt_image, - const ZipModeImage& src_image, - std::vector* split_tgt_images, - std::vector* split_src_images, - std::vector* split_src_ranges); - - 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, - ZipEntry64* entry); - // Return the real size of the zip file. (omit the trailing zeros that used for alignment) - bool GetZipFileSize(size_t* input_file_size); - - static void ValidateSplitImages(const std::vector& split_tgt_images, - const std::vector& split_src_images, - std::vector& split_src_ranges, - size_t total_tgt_size); - // Construct the fake split images based on the chunks info and source ranges; and move them into - // the given vectors. Return true if we add a new split image into |split_tgt_images|, and - // false otherwise. - static bool AddSplitImageFromChunkList(const ZipModeImage& tgt_image, - const ZipModeImage& src_image, - const SortedRangeSet& split_src_ranges, - const std::vector& split_tgt_chunks, - const std::vector& split_src_chunks, - std::vector* split_tgt_images, - std::vector* split_src_images); - - // Function that actually iterates the tgt_chunks and makes patches. - static bool GeneratePatchesInternal(const ZipModeImage& tgt_image, const ZipModeImage& src_image, - std::vector* patch_chunks); - - // size limit in bytes of each chunk. Also, if the length of one zip_entry exceeds the limit, - // we'll split that entry into several smaller chunks in advance. - size_t limit_; -}; - -class ImageModeImage : public Image { - public: - explicit ImageModeImage(bool is_source) : Image(is_source) {} - - // Initialize the image chunks list by searching the magic numbers in an image file. - bool Initialize(const std::string& filename) override; - - bool SetBonusData(const std::vector& bonus_data); - - // In Image Mode, verify that the source and target images have the same chunk structure (ie, the - // same sequence of deflate and normal chunks). - static bool CheckAndProcessChunks(ImageModeImage* tgt_image, ImageModeImage* src_image); - - // In image mode, generate patches against the given source chunks and bonus_data; write the - // result to |patch_name|. - static bool GeneratePatches(const ImageModeImage& tgt_image, const ImageModeImage& src_image, - const std::string& patch_name); -}; - -#endif // _APPLYPATCH_IMGDIFF_IMAGE_H diff --git a/applypatch/include/applypatch/imgpatch.h b/applypatch/include/applypatch/imgpatch.h deleted file mode 100644 index 07c66094..00000000 --- a/applypatch/include/applypatch/imgpatch.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _APPLYPATCH_IMGPATCH_H -#define _APPLYPATCH_IMGPATCH_H - -#include - -#include - -using SinkFn = std::function; - -int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data, - size_t patch_size, SinkFn sink); - -#endif // _APPLYPATCH_IMGPATCH_H diff --git a/applypatch/vendor_flash_recovery.rc b/applypatch/vendor_flash_recovery.rc deleted file mode 100644 index a6003be4..00000000 --- a/applypatch/vendor_flash_recovery.rc +++ /dev/null @@ -1,4 +0,0 @@ -service vendor_flash_recovery /vendor/bin/install-recovery.sh - class main - oneshot - user root diff --git a/bootloader_message/bootloader_message.cpp b/bootloader_message/bootloader_message.cpp index 2d743e73..f010a145 100644 --- a/bootloader_message/bootloader_message.cpp +++ b/bootloader_message/bootloader_message.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -175,6 +176,7 @@ bool write_bootloader_message(const bootloader_message& boot, std::string* err) bool clear_bootloader_message(std::string* err) { bootloader_message boot = {}; + LOG(INFO) << "Clearing BCB"; return write_bootloader_message(boot, err); } @@ -194,11 +196,12 @@ bool write_bootloader_message_to(const std::vector& options, } bool update_bootloader_message(const std::vector& options, std::string* err) { - bootloader_message boot; + bootloader_message boot{}; if (!read_bootloader_message(&boot, err)) { return false; } update_bootloader_message_in_struct(&boot, options); + LOG(INFO) << "Writing BCB " << boot.command << " " << boot.recovery; return write_bootloader_message(boot, err); } @@ -224,7 +227,7 @@ bool update_bootloader_message_in_struct(bootloader_message* boot, } bool write_reboot_bootloader(std::string* err) { - bootloader_message boot; + bootloader_message boot{}; if (!read_bootloader_message(&boot, err)) { return false; } @@ -233,6 +236,7 @@ bool write_reboot_bootloader(std::string* err) { return false; } strlcpy(boot.command, "bootonce-bootloader", sizeof(boot.command)); + LOG(INFO) << "Writing BCB cmd: " << boot.command << " args: " << boot.recovery; return write_bootloader_message(boot, err); } diff --git a/edify/Android.bp b/edify/Android.bp deleted file mode 100644 index 62ff9113..00000000 --- a/edify/Android.bp +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (C) 2017 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "bootable_recovery_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["bootable_recovery_license"], -} - -cc_library_static { - name: "libedify", - - host_supported: true, - vendor_available: true, - recovery_available: true, - - srcs: [ - "expr.cpp", - "lexer.ll", - "parser.yy", - ], - - cflags: [ - "-Wall", - "-Werror", - "-Wno-deprecated-register", - "-Wno-unused-parameter", - ], - - export_include_dirs: [ - "include", - ], - - local_include_dirs: [ - "include", - ], - - static_libs: [ - "libbase", - "libotautil", - ], -} diff --git a/edify/README.md b/edify/README.md deleted file mode 100644 index b3330e23..00000000 --- a/edify/README.md +++ /dev/null @@ -1,111 +0,0 @@ -edify -===== - -Update scripts (from donut onwards) are written in a new little -scripting language ("edify") that is superficially somewhat similar to -the old one ("amend"). This is a brief overview of the new language. - -- The entire script is a single expression. - -- All expressions are string-valued. - -- String literals appear in double quotes. \n, \t, \", and \\ are - understood, as are hexadecimal escapes like \x4a. - -- String literals consisting of only letters, numbers, colons, - underscores, slashes, and periods don't need to be in double quotes. - -- The following words are reserved: - - if then else endif - - They have special meaning when unquoted. (In quotes, they are just - string literals.) - -- When used as a boolean, the empty string is "false" and all other - strings are "true". - -- All functions are actually macros (in the Lisp sense); the body of - the function can control which (if any) of the arguments are - evaluated. This means that functions can act as control - structures. - -- Operators (like "&&" and "||") are just syntactic sugar for builtin - functions, so they can act as control structures as well. - -- ";" is a binary operator; evaluating it just means to first evaluate - the left side, then the right. It can also appear after any - expression. - -- Comments start with "#" and run to the end of the line. - - - -Some examples: - -- There's no distinction between quoted and unquoted strings; the - quotes are only needed if you want characters like whitespace to - appear in the string. The following expressions all evaluate to the - same string. - - "a b" - a + " " + b - "a" + " " + "b" - "a\x20b" - a + "\x20b" - concat(a, " ", "b") - "concat"(a, " ", "b") - - As shown in the last example, function names are just strings, - too. They must be string *literals*, however. This is not legal: - - ("con" + "cat")(a, " ", b) # syntax error! - - -- The ifelse() builtin takes three arguments: it evaluates exactly - one of the second and third, depending on whether the first one is - true. There is also some syntactic sugar to make expressions that - look like if/else statements: - - # these are all equivalent - ifelse(something(), "yes", "no") - if something() then yes else no endif - if something() then "yes" else "no" endif - - The else part is optional. - - if something() then "yes" endif # if something() is false, - # evaluates to false - - ifelse(condition(), "", abort()) # abort() only called if - # condition() is false - - The last example is equivalent to: - - assert(condition()) - - -- The && and || operators can be used similarly; they evaluate their - second argument only if it's needed to determine the truth of the - expression. Their value is the value of the last-evaluated - argument: - - file_exists("/data/system/bad") && delete("/data/system/bad") - - file_exists("/data/system/missing") || create("/data/system/missing") - - get_it() || "xxx" # returns value of get_it() if that value is - # true, otherwise returns "xxx" - - -- The purpose of ";" is to simulate imperative statements, of course, - but the operator can be used anywhere. Its value is the value of - its right side: - - concat(a;b;c, d, e;f) # evaluates to "cdf" - - A more useful example might be something like: - - ifelse(condition(), - (first_step(); second_step();), # second ; is optional - alternative_procedure()) diff --git a/edify/expr.cpp b/edify/expr.cpp deleted file mode 100644 index e5e0e240..00000000 --- a/edify/expr.cpp +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "edify/expr.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include "otautil/error_code.h" - -// Functions should: -// -// - return a malloc()'d string -// - if Evaluate() on any argument returns nullptr, return nullptr. - -static bool BooleanString(const std::string& s) { - return !s.empty(); -} - -bool Evaluate(State* state, const std::unique_ptr& expr, std::string* result) { - if (result == nullptr) { - return false; - } - - std::unique_ptr v(expr->fn(expr->name.c_str(), state, expr->argv)); - if (!v) { - return false; - } - if (v->type != Value::Type::STRING) { - ErrorAbort(state, kArgsParsingFailure, "expecting string, got value type %d", v->type); - return false; - } - - *result = v->data; - return true; -} - -Value* EvaluateValue(State* state, const std::unique_ptr& expr) { - return expr->fn(expr->name.c_str(), state, expr->argv); -} - -Value* StringValue(const char* str) { - if (str == nullptr) { - return nullptr; - } - return new Value(Value::Type::STRING, str); -} - -Value* StringValue(const std::string& str) { - return StringValue(str.c_str()); -} - -Value* ConcatFn(const char* name, State* state, const std::vector>& argv) { - if (argv.empty()) { - return StringValue(""); - } - std::string result; - for (size_t i = 0; i < argv.size(); ++i) { - std::string str; - if (!Evaluate(state, argv[i], &str)) { - return nullptr; - } - result += str; - } - - return StringValue(result); -} - -Value* IfElseFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 2 && argv.size() != 3) { - state->errmsg = "ifelse expects 2 or 3 arguments"; - return nullptr; - } - - std::string cond; - if (!Evaluate(state, argv[0], &cond)) { - return nullptr; - } - - if (!cond.empty()) { - return EvaluateValue(state, argv[1]); - } else if (argv.size() == 3) { - return EvaluateValue(state, argv[2]); - } - - return StringValue(""); -} - -Value* AbortFn(const char* name, State* state, const std::vector>& argv) { - std::string msg; - if (!argv.empty() && Evaluate(state, argv[0], &msg)) { - state->errmsg += msg; - } else { - state->errmsg += "called abort()"; - } - return nullptr; -} - -Value* AssertFn(const char* name, State* state, const std::vector>& argv) { - for (size_t i = 0; i < argv.size(); ++i) { - std::string result; - if (!Evaluate(state, argv[i], &result)) { - return nullptr; - } - if (result.empty()) { - int len = argv[i]->end - argv[i]->start; - state->errmsg = "assert failed: " + state->script.substr(argv[i]->start, len); - return nullptr; - } - } - return StringValue(""); -} - -Value* SleepFn(const char* name, State* state, const std::vector>& argv) { - std::string val; - if (!Evaluate(state, argv[0], &val)) { - return nullptr; - } - - int v; - if (!android::base::ParseInt(val.c_str(), &v, 0)) { - return nullptr; - } - sleep(v); - - return StringValue(val); -} - -Value* StdoutFn(const char* name, State* state, const std::vector>& argv) { - for (size_t i = 0; i < argv.size(); ++i) { - std::string v; - if (!Evaluate(state, argv[i], &v)) { - return nullptr; - } - fputs(v.c_str(), stdout); - } - return StringValue(""); -} - -Value* LogicalAndFn(const char* name, State* state, - const std::vector>& argv) { - std::string left; - if (!Evaluate(state, argv[0], &left)) { - return nullptr; - } - if (BooleanString(left)) { - return EvaluateValue(state, argv[1]); - } else { - return StringValue(""); - } -} - -Value* LogicalOrFn(const char* name, State* state, - const std::vector>& argv) { - std::string left; - if (!Evaluate(state, argv[0], &left)) { - return nullptr; - } - if (!BooleanString(left)) { - return EvaluateValue(state, argv[1]); - } else { - return StringValue(left); - } -} - -Value* LogicalNotFn(const char* name, State* state, - const std::vector>& argv) { - std::string val; - if (!Evaluate(state, argv[0], &val)) { - return nullptr; - } - - return StringValue(BooleanString(val) ? "" : "t"); -} - -Value* SubstringFn(const char* name, State* state, - const std::vector>& argv) { - std::string needle; - if (!Evaluate(state, argv[0], &needle)) { - return nullptr; - } - - std::string haystack; - if (!Evaluate(state, argv[1], &haystack)) { - return nullptr; - } - - std::string result = (haystack.find(needle) != std::string::npos) ? "t" : ""; - return StringValue(result); -} - -Value* EqualityFn(const char* name, State* state, const std::vector>& argv) { - std::string left; - if (!Evaluate(state, argv[0], &left)) { - return nullptr; - } - std::string right; - if (!Evaluate(state, argv[1], &right)) { - return nullptr; - } - - const char* result = (left == right) ? "t" : ""; - return StringValue(result); -} - -Value* InequalityFn(const char* name, State* state, - const std::vector>& argv) { - std::string left; - if (!Evaluate(state, argv[0], &left)) { - return nullptr; - } - std::string right; - if (!Evaluate(state, argv[1], &right)) { - return nullptr; - } - - const char* result = (left != right) ? "t" : ""; - return StringValue(result); -} - -Value* SequenceFn(const char* name, State* state, const std::vector>& argv) { - std::unique_ptr left(EvaluateValue(state, argv[0])); - if (!left) { - return nullptr; - } - return EvaluateValue(state, argv[1]); -} - -Value* LessThanIntFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 2) { - state->errmsg = "less_than_int expects 2 arguments"; - return nullptr; - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return nullptr; - } - - // Parse up to at least long long or 64-bit integers. - int64_t l_int; - if (!android::base::ParseInt(args[0].c_str(), &l_int)) { - state->errmsg = "failed to parse int in " + args[0]; - return nullptr; - } - - int64_t r_int; - if (!android::base::ParseInt(args[1].c_str(), &r_int)) { - state->errmsg = "failed to parse int in " + args[1]; - return nullptr; - } - - return StringValue(l_int < r_int ? "t" : ""); -} - -Value* GreaterThanIntFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 2) { - state->errmsg = "greater_than_int expects 2 arguments"; - return nullptr; - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return nullptr; - } - - // Parse up to at least long long or 64-bit integers. - int64_t l_int; - if (!android::base::ParseInt(args[0].c_str(), &l_int)) { - state->errmsg = "failed to parse int in " + args[0]; - return nullptr; - } - - int64_t r_int; - if (!android::base::ParseInt(args[1].c_str(), &r_int)) { - state->errmsg = "failed to parse int in " + args[1]; - return nullptr; - } - - return StringValue(l_int > r_int ? "t" : ""); -} - -Value* Literal(const char* name, State* state, const std::vector>& argv) { - return StringValue(name); -} - -// ----------------------------------------------------------------- -// the function table -// ----------------------------------------------------------------- - -static std::unordered_map fn_table; - -void RegisterFunction(const std::string& name, Function fn) { - fn_table[name] = fn; -} - -Function FindFunction(const std::string& name) { - if (fn_table.find(name) == fn_table.end()) { - return nullptr; - } else { - return fn_table[name]; - } -} - -void RegisterBuiltins() { - RegisterFunction("ifelse", IfElseFn); - RegisterFunction("abort", AbortFn); - RegisterFunction("assert", AssertFn); - RegisterFunction("concat", ConcatFn); - RegisterFunction("is_substring", SubstringFn); - RegisterFunction("stdout", StdoutFn); - RegisterFunction("sleep", SleepFn); - - RegisterFunction("less_than_int", LessThanIntFn); - RegisterFunction("greater_than_int", GreaterThanIntFn); -} - - -// ----------------------------------------------------------------- -// convenience methods for functions -// ----------------------------------------------------------------- - -// Evaluate the expressions in argv, and put the results of strings in args. If any expression -// evaluates to nullptr, return false. Return true on success. -bool ReadArgs(State* state, const std::vector>& argv, - std::vector* args) { - return ReadArgs(state, argv, args, 0, argv.size()); -} - -bool ReadArgs(State* state, const std::vector>& argv, - std::vector* args, size_t start, size_t len) { - if (args == nullptr) { - return false; - } - if (start + len > argv.size()) { - return false; - } - for (size_t i = start; i < start + len; ++i) { - std::string var; - if (!Evaluate(state, argv[i], &var)) { - args->clear(); - return false; - } - args->push_back(var); - } - return true; -} - -// Evaluate the expressions in argv, and put the results of Value* in args. If any expression -// evaluate to nullptr, return false. Return true on success. -bool ReadValueArgs(State* state, const std::vector>& argv, - std::vector>* args) { - return ReadValueArgs(state, argv, args, 0, argv.size()); -} - -bool ReadValueArgs(State* state, const std::vector>& argv, - std::vector>* args, size_t start, size_t len) { - if (args == nullptr) { - return false; - } - if (len == 0 || start + len > argv.size()) { - return false; - } - for (size_t i = start; i < start + len; ++i) { - std::unique_ptr v(EvaluateValue(state, argv[i])); - if (!v) { - args->clear(); - return false; - } - args->push_back(std::move(v)); - } - return true; -} - -// Use printf-style arguments to compose an error message to put into -// *state. Returns nullptr. -Value* ErrorAbort(State* state, const char* format, ...) { - va_list ap; - va_start(ap, format); - android::base::StringAppendV(&state->errmsg, format, ap); - va_end(ap); - return nullptr; -} - -Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) { - std::string err_message; - va_list ap; - va_start(ap, format); - android::base::StringAppendV(&err_message, format, ap); - va_end(ap); - // Ensure that there's exactly one line break at the end of the error message. - state->errmsg = android::base::Trim(err_message) + "\n"; - state->cause_code = cause_code; - return nullptr; -} - -State::State(const std::string& script, UpdaterInterface* interface) - : script(script), updater(interface), error_code(kNoError), cause_code(kNoCause) {} diff --git a/edify/include/edify/expr.h b/edify/include/edify/expr.h deleted file mode 100644 index 3ddf7f5f..00000000 --- a/edify/include/edify/expr.h +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _EXPRESSION_H -#define _EXPRESSION_H - -#include - -#include -#include -#include - -#include "edify/updater_interface.h" - -// Forward declaration to avoid including "otautil/error_code.h". -enum ErrorCode : int; -enum CauseCode : int; - -struct State { - State(const std::string& script, UpdaterInterface* cookie); - - // The source of the original script. - const std::string& script; - - // A pointer to app-specific data; the libedify doesn't use this value. - UpdaterInterface* updater; - - // The error message (if any) returned if the evaluation aborts. - // Should be empty initially, will be either empty or a string that - // Evaluate() returns. - std::string errmsg; - - // error code indicates the type of failure (e.g. failure to update system image) - // during the OTA process. - ErrorCode error_code; - - // cause code provides more detailed reason of an OTA failure (e.g. fsync error) - // in addition to the error code. - CauseCode cause_code; - - bool is_retry = false; -}; - -struct Value { - enum class Type { - STRING = 1, - BLOB = 2, - }; - - Value(Type type, std::string str) : type(type), data(std::move(str)) {} - - Type type; - std::string data; -}; - -struct Expr; - -using Function = Value* (*)(const char* name, State* state, - const std::vector>& argv); - -struct Expr { - Function fn; - std::string name; - std::vector> argv; - int start, end; - - Expr(Function fn, const std::string& name, int start, int end) : - fn(fn), - name(name), - start(start), - end(end) {} -}; - -// Evaluate the input expr, return the resulting Value. -Value* EvaluateValue(State* state, const std::unique_ptr& expr); - -// Evaluate the input expr, assert that it is a string, and update the result parameter. This -// function returns true if the evaluation succeeds. This is a convenience function for older -// functions that want to deal only with strings. -bool Evaluate(State* state, const std::unique_ptr& expr, std::string* result); - -// Glue to make an Expr out of a literal. -Value* Literal(const char* name, State* state, const std::vector>& argv); - -// Functions corresponding to various syntactic sugar operators. -// ("concat" is also available as a builtin function, to concatenate -// more than two strings.) -Value* ConcatFn(const char* name, State* state, const std::vector>& argv); -Value* LogicalAndFn(const char* name, State* state, const std::vector>& argv); -Value* LogicalOrFn(const char* name, State* state, const std::vector>& argv); -Value* LogicalNotFn(const char* name, State* state, const std::vector>& argv); -Value* SubstringFn(const char* name, State* state, const std::vector>& argv); -Value* EqualityFn(const char* name, State* state, const std::vector>& argv); -Value* InequalityFn(const char* name, State* state, const std::vector>& argv); -Value* SequenceFn(const char* name, State* state, const std::vector>& argv); - -// Global builtins, registered by RegisterBuiltins(). -Value* IfElseFn(const char* name, State* state, const std::vector>& argv); -Value* AssertFn(const char* name, State* state, const std::vector>& argv); -Value* AbortFn(const char* name, State* state, const std::vector>& argv); - -// Register a new function. The same Function may be registered under -// multiple names, but a given name should only be used once. -void RegisterFunction(const std::string& name, Function fn); - -// Register all the builtins. -void RegisterBuiltins(); - -// Find the Function for a given name; return NULL if no such function -// exists. -Function FindFunction(const std::string& name); - -// --- convenience functions for use in functions --- - -// Evaluate the expressions in argv, and put the results of strings in args. If any expression -// evaluates to nullptr, return false. Return true on success. -bool ReadArgs(State* state, const std::vector>& argv, - std::vector* args); -bool ReadArgs(State* state, const std::vector>& argv, - std::vector* args, size_t start, size_t len); - -// Evaluate the expressions in argv, and put the results of Value* in args. If any -// expression evaluate to nullptr, return false. Return true on success. -bool ReadValueArgs(State* state, const std::vector>& argv, - std::vector>* args); -bool ReadValueArgs(State* state, const std::vector>& argv, - std::vector>* args, size_t start, size_t len); - -// Use printf-style arguments to compose an error message to put into -// *state. Returns NULL. -Value* ErrorAbort(State* state, const char* format, ...) - __attribute__((format(printf, 2, 3), deprecated)); - -// ErrorAbort has an optional (but recommended) argument 'cause_code'. If the cause code -// is set, it will be logged into last_install and provides reason of OTA failures. -Value* ErrorAbort(State* state, CauseCode cause_code, const char* format, ...) - __attribute__((format(printf, 3, 4))); - -// Copying the string into a Value. -Value* StringValue(const char* str); - -Value* StringValue(const std::string& str); - -int ParseString(const std::string& str, std::unique_ptr* root, int* error_count); - -#endif // _EXPRESSION_H diff --git a/edify/include/edify/updater_interface.h b/edify/include/edify/updater_interface.h deleted file mode 100644 index aa977e3c..00000000 --- a/edify/include/edify/updater_interface.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include -#include - -struct ZipArchive; -typedef ZipArchive* ZipArchiveHandle; - -class UpdaterRuntimeInterface; - -class UpdaterInterface { - public: - virtual ~UpdaterInterface() = default; - - // Writes the message to command pipe, adds a new line in the end. - virtual void WriteToCommandPipe(const std::string_view message, bool flush = false) const = 0; - - // Sends over the message to recovery to print it on the screen. - virtual void UiPrint(const std::string_view message) const = 0; - - // Given the name of the block device, returns |name| for updates on the device; or the file path - // to the fake block device for simulations. - virtual std::string FindBlockDeviceName(const std::string_view name) const = 0; - - virtual UpdaterRuntimeInterface* GetRuntime() const = 0; - virtual ZipArchiveHandle GetPackageHandle() const = 0; - virtual std::string GetResult() const = 0; - virtual uint8_t* GetMappedPackageAddress() const = 0; - virtual size_t GetMappedPackageLength() const = 0; -}; diff --git a/edify/include/edify/updater_runtime_interface.h b/edify/include/edify/updater_runtime_interface.h deleted file mode 100644 index bdd6aecc..00000000 --- a/edify/include/edify/updater_runtime_interface.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -// This class serves as the base to updater runtime. It wraps the runtime dependent functions; and -// updates on device and host simulations can have different implementations. e.g. block devices -// during host simulation merely a temporary file. With this class, the caller side in registered -// updater's functions will stay the same for both update and simulation. -class UpdaterRuntimeInterface { - public: - virtual ~UpdaterRuntimeInterface() = default; - - // Returns true if it's a runtime instance for simulation. - virtual bool IsSimulator() const = 0; - - // Returns the value of system property |key|. If the property doesn't exist, returns - // |default_value|. - virtual std::string GetProperty(const std::string_view key, - const std::string_view default_value) const = 0; - - // Given the name of the block device, returns |name| for updates on the device; or the file path - // to the fake block device for simulations. - virtual std::string FindBlockDeviceName(const std::string_view name) const = 0; - - // Mounts the |location| on |mount_point|. Returns 0 on success. - virtual int Mount(const std::string_view location, const std::string_view mount_point, - const std::string_view fs_type, const std::string_view mount_options) = 0; - - // Returns true if |mount_point| is mounted. - virtual bool IsMounted(const std::string_view mount_point) const = 0; - - // Unmounts the |mount_point|. Returns a pair of results with the first value indicating - // if the |mount_point| is mounted, and the second value indicating the result of umount(2). - virtual std::pair Unmount(const std::string_view mount_point) = 0; - - // Reads |filename| and puts its value to |content|. - virtual bool ReadFileToString(const std::string_view filename, std::string* content) const = 0; - - // Updates the content of |filename| with |content|. - virtual bool WriteStringToFile(const std::string_view content, - const std::string_view filename) const = 0; - - // Wipes the first |len| bytes of block device in |filename|. - virtual int WipeBlockDevice(const std::string_view filename, size_t len) const = 0; - - // Starts a child process and runs the program with |args|. Uses vfork(2) if |is_vfork| is true. - virtual int RunProgram(const std::vector& args, bool is_vfork) const = 0; - - // Runs tune2fs with arguments |args|. - virtual int Tune2Fs(const std::vector& args) const = 0; - - // Dynamic partition related functions. - virtual bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) = 0; - virtual bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) = 0; - virtual bool UpdateDynamicPartitions(const std::string_view op_list_value) = 0; - - // On devices supports A/B, add current slot suffix to arg. Otherwise, return |arg| as is. - virtual std::string AddSlotSuffix(const std::string_view arg) const = 0; -}; diff --git a/edify/lexer.ll b/edify/lexer.ll deleted file mode 100644 index 4e04003b..00000000 --- a/edify/lexer.ll +++ /dev/null @@ -1,112 +0,0 @@ -%{ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include - -#include "edify/expr.h" -#include "yydefs.h" -#include "parser.h" - -int gLine = 1; -int gColumn = 1; -int gPos = 0; - -std::string string_buffer; - -#define ADVANCE do {yylloc.start=gPos; yylloc.end=gPos+yyleng; \ - gColumn+=yyleng; gPos+=yyleng;} while(0) - -%} - -%x STR - -%option noinput -%option nounput -%option noyywrap - -%% - - -\" { - BEGIN(STR); - string_buffer.clear(); - yylloc.start = gPos; - ++gColumn; - ++gPos; -} - -{ - \" { - ++gColumn; - ++gPos; - BEGIN(INITIAL); - yylval.str = strdup(string_buffer.c_str()); - yylloc.end = gPos; - return STRING; - } - - \\n { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\n'); } - \\t { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\t'); } - \\\" { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\"'); } - \\\\ { gColumn += yyleng; gPos += yyleng; string_buffer.push_back('\\'); } - - \\x[0-9a-fA-F]{2} { - gColumn += yyleng; - gPos += yyleng; - int val; - sscanf(yytext+2, "%x", &val); - string_buffer.push_back(static_cast(val)); - } - - \n { - ++gLine; - ++gPos; - gColumn = 1; - string_buffer.push_back(yytext[0]); - } - - . { - ++gColumn; - ++gPos; - string_buffer.push_back(yytext[0]); - } -} - -if ADVANCE; return IF; -then ADVANCE; return THEN; -else ADVANCE; return ELSE; -endif ADVANCE; return ENDIF; - -[a-zA-Z0-9_:/.]+ { - ADVANCE; - yylval.str = strdup(yytext); - return STRING; -} - -\&\& ADVANCE; return AND; -\|\| ADVANCE; return OR; -== ADVANCE; return EQ; -!= ADVANCE; return NE; - -[+(),!;] ADVANCE; return yytext[0]; - -[ \t]+ ADVANCE; - -(#.*)?\n gPos += yyleng; ++gLine; gColumn = 1; - -. return BAD; diff --git a/edify/parser.yy b/edify/parser.yy deleted file mode 100644 index 5e1e847a..00000000 --- a/edify/parser.yy +++ /dev/null @@ -1,145 +0,0 @@ -%{ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "edify/expr.h" -#include "yydefs.h" -#include "parser.h" - -extern int gLine; -extern int gColumn; - -void yyerror(std::unique_ptr* root, int* error_count, const char* s); -int yyparse(std::unique_ptr* root, int* error_count); - -struct yy_buffer_state; -void yy_switch_to_buffer(struct yy_buffer_state* new_buffer); -struct yy_buffer_state* yy_scan_string(const char* yystr); - -// Convenience function for building expressions with a fixed number -// of arguments. -static Expr* Build(Function fn, YYLTYPE loc, size_t count, ...) { - va_list v; - va_start(v, count); - Expr* e = new Expr(fn, "(operator)", loc.start, loc.end); - for (size_t i = 0; i < count; ++i) { - e->argv.emplace_back(va_arg(v, Expr*)); - } - va_end(v); - return e; -} - -%} - -%locations - -%union { - char* str; - Expr* expr; - std::vector>* args; -} - -%token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF -%token STRING BAD -%type expr -%type arglist - -%destructor { delete $$; } expr -%destructor { delete $$; } arglist - -%parse-param {std::unique_ptr* root} -%parse-param {int* error_count} -%define parse.error verbose - -/* declarations in increasing order of precedence */ -%left ';' -%left ',' -%left OR -%left AND -%left EQ NE -%left '+' -%right '!' - -%% - -input: expr { root->reset($1); } -; - -expr: STRING { - $$ = new Expr(Literal, $1, @$.start, @$.end); -} -| '(' expr ')' { $$ = $2; $$->start=@$.start; $$->end=@$.end; } -| expr ';' { $$ = $1; $$->start=@1.start; $$->end=@1.end; } -| expr ';' expr { $$ = Build(SequenceFn, @$, 2, $1, $3); } -| error ';' expr { $$ = $3; $$->start=@$.start; $$->end=@$.end; } -| expr '+' expr { $$ = Build(ConcatFn, @$, 2, $1, $3); } -| expr EQ expr { $$ = Build(EqualityFn, @$, 2, $1, $3); } -| expr NE expr { $$ = Build(InequalityFn, @$, 2, $1, $3); } -| expr AND expr { $$ = Build(LogicalAndFn, @$, 2, $1, $3); } -| expr OR expr { $$ = Build(LogicalOrFn, @$, 2, $1, $3); } -| '!' expr { $$ = Build(LogicalNotFn, @$, 1, $2); } -| IF expr THEN expr ENDIF { $$ = Build(IfElseFn, @$, 2, $2, $4); } -| IF expr THEN expr ELSE expr ENDIF { $$ = Build(IfElseFn, @$, 3, $2, $4, $6); } -| STRING '(' arglist ')' { - Function fn = FindFunction($1); - if (fn == nullptr) { - std::string msg = "unknown function \"" + std::string($1) + "\""; - yyerror(root, error_count, msg.c_str()); - YYERROR; - } - $$ = new Expr(fn, $1, @$.start, @$.end); - $$->argv = std::move(*$3); -} -; - -arglist: /* empty */ { - $$ = new std::vector>; -} -| expr { - $$ = new std::vector>; - $$->emplace_back($1); -} -| arglist ',' expr { - UNUSED($1); - $$->push_back(std::unique_ptr($3)); -} -; - -%% - -void yyerror(std::unique_ptr* root, int* error_count, const char* s) { - if (strlen(s) == 0) { - s = "syntax error"; - } - printf("line %d col %d: %s\n", gLine, gColumn, s); - ++*error_count; -} - -int ParseString(const std::string& str, std::unique_ptr* root, int* error_count) { - yy_switch_to_buffer(yy_scan_string(str.c_str())); - return yyparse(root, error_count); -} diff --git a/edify/yydefs.h b/edify/yydefs.h deleted file mode 100644 index aca398fb..00000000 --- a/edify/yydefs.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _YYDEFS_H_ -#define _YYDEFS_H_ - -#define YYLTYPE YYLTYPE -typedef struct { - int start, end; -} YYLTYPE; - -#define YYLLOC_DEFAULT(Current, Rhs, N) \ - do { \ - if (N) { \ - (Current).start = YYRHSLOC(Rhs, 1).start; \ - (Current).end = YYRHSLOC(Rhs, N).end; \ - } else { \ - (Current).start = YYRHSLOC(Rhs, 0).start; \ - (Current).end = YYRHSLOC(Rhs, 0).end; \ - } \ - } while (0) - -int yylex(); - -#endif diff --git a/recovery_utils/Android.bp b/recovery_utils/Android.bp index c31c4073..a48ce002 100644 --- a/recovery_utils/Android.bp +++ b/recovery_utils/Android.bp @@ -98,5 +98,6 @@ cc_library_static { "//bootable/recovery/install", "//bootable/recovery/minadbd", "//bootable/recovery/tests", + "//bootable/deprecated-ota:__subpackages__", ], } diff --git a/tests/Android.bp b/tests/Android.bp index 1fd26c4b..54e11738 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -59,24 +59,6 @@ cc_defaults { }, } -// libapplypatch, libapplypatch_modes -libapplypatch_static_libs = [ - "libapplypatch_modes", - "libapplypatch", - "libedify", - "libotautil", - "libbsdiff", - "libbspatch", - "libdivsufsort", - "libdivsufsort64", - "libutils", - "libbase", - "libbrotli", - "libbz", - "libz_stable", - "libziparchive", -] - // librecovery_defaults uses many shared libs that we want to avoid using in tests (e.g. we don't // have 32-bit android.hardware.health@2.0.so or libbootloader_message.so on marlin). librecovery_static_libs = [ @@ -125,16 +107,10 @@ cc_test { defaults: [ "recovery_test_defaults", - "libupdater_defaults", - "libupdater_device_defaults", ], test_suites: ["device-tests"], - tidy_timeout_srcs: [ - "unit/commands_test.cpp", - ], - srcs: [ "unit/*.cpp", ], @@ -143,7 +119,7 @@ cc_test { "libbinder_ndk", ], - static_libs: libapplypatch_static_libs + librecovery_static_libs + [ + static_libs: librecovery_static_libs + [ "android.hardware.health-translate-ndk", "android.hardware.health-V3-ndk", "libhealthshim", @@ -152,8 +128,6 @@ cc_test { "libminui", "librecovery_utils", "libotautil", - "libupdater_device", - "libupdater_core", "libupdate_verifier", "libprotobuf-cpp-lite", @@ -184,47 +158,6 @@ cc_test { ], } -cc_test_host { - name: "recovery_host_test", - isolated: true, - - defaults: [ - "recovery_test_defaults", - "libupdater_defaults", - ], - - tidy_timeout_srcs: [ - "unit/host/imgdiff_test.cpp", - ], - - srcs: [ - "unit/host/*", - ], - - static_libs: [ - "libupdater_host", - "libupdater_core", - "libimgdiff", - "libbsdiff", - "libdivsufsort64", - "libdivsufsort", - "libfstab", - "libc++fs", - ], - - test_suites: ["general-tests"], - test_config: "RecoveryHostTest.xml", - - data: ["testdata/*"], - - target: { - darwin: { - // libapplypatch in "libupdater_defaults" is not available on the Mac. - enabled: false, - }, - }, -} - cc_fuzz { name: "libinstall_verify_package_fuzzer", defaults: [ diff --git a/tests/RecoveryHostTest.xml b/tests/RecoveryHostTest.xml deleted file mode 100644 index 0ac75e4e..00000000 --- a/tests/RecoveryHostTest.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - diff --git a/tests/unit/applypatch_modes_test.cpp b/tests/unit/applypatch_modes_test.cpp deleted file mode 100644 index 08414b79..00000000 --- a/tests/unit/applypatch_modes_test.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agree to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "applypatch/applypatch_modes.h" -#include "common/test_constants.h" -#include "otautil/paths.h" -#include "otautil/print_sha1.h" -#include "otautil/sysutil.h" - -using namespace std::string_literals; - -// Loads a given partition and returns a string of form "EMMC:name:size:hash". -static std::string GetEmmcTargetString(const std::string& filename, - const std::string& display_name = "") { - std::string data; - if (!android::base::ReadFileToString(filename, &data)) { - PLOG(ERROR) << "Failed to read " << filename; - return {}; - } - - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast(data.c_str()), data.size(), digest); - - return "EMMC:"s + (display_name.empty() ? filename : display_name) + ":" + - std::to_string(data.size()) + ":" + print_sha1(digest); -} - -class ApplyPatchModesTest : public ::testing::Test { - protected: - void SetUp() override { - source = GetEmmcTargetString(from_testdata_base("boot.img")); - ASSERT_FALSE(source.empty()); - - std::string recovery_file = from_testdata_base("recovery.img"); - recovery = GetEmmcTargetString(recovery_file); - ASSERT_FALSE(recovery.empty()); - - ASSERT_TRUE(android::base::WriteStringToFile("", patched_file_.path)); - target = GetEmmcTargetString(recovery_file, patched_file_.path); - ASSERT_FALSE(target.empty()); - - Paths::Get().set_cache_temp_source(cache_source_.path); - } - - std::string source; - std::string target; - std::string recovery; - - private: - TemporaryFile cache_source_; - TemporaryFile patched_file_; -}; - -static int InvokeApplyPatchModes(const std::vector& args) { - auto args_to_call = StringVectorToNullTerminatedArray(args); - return applypatch_modes(args_to_call.size() - 1, args_to_call.data()); -} - -static void VerifyPatchedTarget(const std::string& target) { - std::vector pieces = android::base::Split(target, ":"); - ASSERT_EQ(4, pieces.size()); - ASSERT_EQ("EMMC", pieces[0]); - - std::string patched_emmc = GetEmmcTargetString(pieces[1]); - ASSERT_FALSE(patched_emmc.empty()); - ASSERT_EQ(target, patched_emmc); -} - -TEST_F(ApplyPatchModesTest, InvalidArgs) { - // At least two args (including the filename). - ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch" })); - - // Unrecognized args. - ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch", "-x" })); -} - -TEST_F(ApplyPatchModesTest, PatchModeEmmcTarget) { - std::vector args{ - "applypatch", - "--bonus", - from_testdata_base("bonus.file"), - "--patch", - from_testdata_base("recovery-from-boot.p"), - "--target", - target, - "--source", - source, - }; - ASSERT_EQ(0, InvokeApplyPatchModes(args)); - VerifyPatchedTarget(target); -} - -// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has -// everything). -TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithoutBonusFile) { - std::vector args{ - "applypatch", "--patch", from_testdata_base("recovery-from-boot-with-bonus.p"), - "--target", target, "--source", - source, - }; - - ASSERT_EQ(0, InvokeApplyPatchModes(args)); - VerifyPatchedTarget(target); -} - -// Ensures that applypatch works with a bsdiff based recovery-from-boot.p. -TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) { - // Generate the bsdiff patch of recovery-from-boot.p. - std::string src_content; - ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("boot.img"), &src_content)); - - std::string tgt_content; - ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("recovery.img"), &tgt_content)); - - TemporaryFile patch_file; - ASSERT_EQ(0, - bsdiff::bsdiff(reinterpret_cast(src_content.data()), src_content.size(), - reinterpret_cast(tgt_content.data()), tgt_content.size(), - patch_file.path, nullptr)); - - std::vector args{ - "applypatch", "--patch", patch_file.path, "--target", target, "--source", source, - }; - ASSERT_EQ(0, InvokeApplyPatchModes(args)); - VerifyPatchedTarget(target); -} - -TEST_F(ApplyPatchModesTest, PatchModeInvalidArgs) { - // Invalid bonus file. - std::vector args{ - "applypatch", "--bonus", "/doesntexist", "--patch", from_testdata_base("recovery-from-boot.p"), - "--target", target, "--source", source, - }; - ASSERT_NE(0, InvokeApplyPatchModes(args)); - - // With bonus file, but missing args. - ASSERT_NE(0, - InvokeApplyPatchModes({ "applypatch", "--bonus", from_testdata_base("bonus.file") })); -} - -TEST_F(ApplyPatchModesTest, FlashMode) { - std::vector args{ - "applypatch", "--flash", from_testdata_base("recovery.img"), "--target", target, - }; - ASSERT_EQ(0, InvokeApplyPatchModes(args)); - VerifyPatchedTarget(target); -} - -TEST_F(ApplyPatchModesTest, FlashModeInvalidArgs) { - std::vector args{ - "applypatch", "--bonus", from_testdata_base("bonus.file"), "--flash", source, - "--target", target, - }; - ASSERT_NE(0, InvokeApplyPatchModes(args)); -} - -TEST_F(ApplyPatchModesTest, CheckMode) { - ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--check", recovery })); - ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--check", source })); -} - -TEST_F(ApplyPatchModesTest, CheckModeInvalidArgs) { - ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch", "--check" })); -} - -TEST_F(ApplyPatchModesTest, CheckModeNonEmmcTarget) { - ASSERT_NE(0, InvokeApplyPatchModes({ "applypatch", "--check", from_testdata_base("boot.img") })); -} - -TEST_F(ApplyPatchModesTest, ShowLicenses) { - ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--license" })); -} diff --git a/tests/unit/applypatch_test.cpp b/tests/unit/applypatch_test.cpp deleted file mode 100644 index 218a224f..00000000 --- a/tests/unit/applypatch_test.cpp +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agree to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "applypatch/applypatch.h" -#include "common/test_constants.h" -#include "edify/expr.h" -#include "otautil/paths.h" -#include "otautil/print_sha1.h" - -using namespace std::string_literals; - -class ApplyPatchTest : public ::testing::Test { - protected: - void SetUp() override { - source_file = from_testdata_base("boot.img"); - FileContents boot_fc; - ASSERT_TRUE(LoadFileContents(source_file, &boot_fc)); - source_size = boot_fc.data.size(); - source_sha1 = print_sha1(boot_fc.sha1); - - target_file = from_testdata_base("recovery.img"); - FileContents recovery_fc; - ASSERT_TRUE(LoadFileContents(target_file, &recovery_fc)); - target_size = recovery_fc.data.size(); - target_sha1 = print_sha1(recovery_fc.sha1); - - source_partition = Partition(source_file, source_size, source_sha1); - target_partition = Partition(partition_file.path, target_size, target_sha1); - - srand(time(nullptr)); - bad_sha1_a = android::base::StringPrintf("%040x", rand()); - bad_sha1_b = android::base::StringPrintf("%040x", rand()); - - // Reset the cache backup file. - Paths::Get().set_cache_temp_source(cache_temp_source.path); - } - - void TearDown() override { - ASSERT_TRUE(android::base::RemoveFileIfExists(cache_temp_source.path)); - } - - std::string source_file; - std::string source_sha1; - size_t source_size; - - std::string target_file; - std::string target_sha1; - size_t target_size; - - std::string bad_sha1_a; - std::string bad_sha1_b; - - Partition source_partition; - Partition target_partition; - - private: - TemporaryFile partition_file; - TemporaryFile cache_temp_source; -}; - -TEST_F(ApplyPatchTest, CheckPartition) { - ASSERT_TRUE(CheckPartition(source_partition)); -} - -TEST_F(ApplyPatchTest, CheckPartition_Mismatching) { - ASSERT_FALSE(CheckPartition(Partition(source_file, target_size, target_sha1))); - ASSERT_FALSE(CheckPartition(Partition(source_file, source_size, bad_sha1_a))); - - ASSERT_FALSE(CheckPartition(Partition(source_file, source_size - 1, source_sha1))); - ASSERT_FALSE(CheckPartition(Partition(source_file, source_size + 1, source_sha1))); -} - -TEST_F(ApplyPatchTest, PatchPartitionCheck) { - ASSERT_TRUE(PatchPartitionCheck(target_partition, source_partition)); - - ASSERT_TRUE( - PatchPartitionCheck(Partition(source_file, source_size - 1, source_sha1), source_partition)); - - ASSERT_TRUE( - PatchPartitionCheck(Partition(source_file, source_size + 1, source_sha1), source_partition)); -} - -TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup) { - ASSERT_FALSE( - PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1))); - - Paths::Get().set_cache_temp_source(source_file); - ASSERT_TRUE( - PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1))); -} - -TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup_BothCorrupted) { - ASSERT_FALSE( - PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1))); - - Paths::Get().set_cache_temp_source(target_file); - ASSERT_FALSE( - PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1))); -} - -TEST_F(ApplyPatchTest, PatchPartition) { - FileContents patch_fc; - ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot.p"), &patch_fc)); - Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend())); - - FileContents bonus_fc; - ASSERT_TRUE(LoadFileContents(from_testdata_base("bonus.file"), &bonus_fc)); - Value bonus(Value::Type::BLOB, std::string(bonus_fc.data.cbegin(), bonus_fc.data.cend())); - - ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus, false)); -} - -// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has -// everything). -TEST_F(ApplyPatchTest, PatchPartitionWithoutBonusFile) { - FileContents patch_fc; - ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot-with-bonus.p"), &patch_fc)); - Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend())); - - ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr, false)); -} - -class FreeCacheTest : public ::testing::Test { - protected: - static constexpr size_t PARTITION_SIZE = 4096 * 10; - - // Returns a sorted list of files in |dirname|. - static std::vector FindFilesInDir(const std::string& dirname) { - std::vector file_list; - - std::unique_ptr d(opendir(dirname.c_str()), closedir); - struct dirent* de; - while ((de = readdir(d.get())) != 0) { - std::string path = dirname + "/" + de->d_name; - - struct stat st; - if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) { - file_list.emplace_back(de->d_name); - } - } - - std::sort(file_list.begin(), file_list.end()); - return file_list; - } - - void AddFilesToDir(const std::string& dir, const std::vector& files) { - std::string zeros(4096, 0); - for (const auto& file : files) { - temporary_files_.push_back(dir + "/" + file); - ASSERT_TRUE(android::base::WriteStringToFile(zeros, temporary_files_.back())); - } - } - - void SetUp() override { - Paths::Get().set_cache_log_directory(mock_log_dir.path); - temporary_files_.clear(); - } - - void TearDown() override { - for (const auto& file : temporary_files_) { - ASSERT_TRUE(android::base::RemoveFileIfExists(file)); - } - } - - // A mock method to calculate the free space. It assumes the partition has a total size of 40960 - // bytes and all files are 4096 bytes in size. - static size_t MockFreeSpaceChecker(const std::string& dirname) { - std::vector files = FindFilesInDir(dirname); - return PARTITION_SIZE - 4096 * files.size(); - } - - TemporaryDir mock_cache; - TemporaryDir mock_log_dir; - - private: - std::vector temporary_files_; -}; - -TEST_F(FreeCacheTest, FreeCacheSmoke) { - std::vector files = { "file1", "file2", "file3" }; - AddFilesToDir(mock_cache.path, files); - ASSERT_EQ(files, FindFilesInDir(mock_cache.path)); - ASSERT_EQ(4096 * 7, MockFreeSpaceChecker(mock_cache.path)); - - ASSERT_TRUE(RemoveFilesInDirectory(4096 * 9, mock_cache.path, MockFreeSpaceChecker)); - - ASSERT_EQ(std::vector{ "file3" }, FindFilesInDir(mock_cache.path)); - ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_cache.path)); -} - -TEST_F(FreeCacheTest, FreeCacheFreeSpaceCheckerError) { - std::vector files{ "file1", "file2", "file3" }; - AddFilesToDir(mock_cache.path, files); - ASSERT_EQ(files, FindFilesInDir(mock_cache.path)); - ASSERT_EQ(4096 * 7, MockFreeSpaceChecker(mock_cache.path)); - - ASSERT_FALSE( - RemoveFilesInDirectory(4096 * 9, mock_cache.path, [](const std::string&) { return -1; })); -} - -TEST_F(FreeCacheTest, FreeCacheOpenFile) { - std::vector files = { "file1", "file2" }; - AddFilesToDir(mock_cache.path, files); - ASSERT_EQ(files, FindFilesInDir(mock_cache.path)); - ASSERT_EQ(4096 * 8, MockFreeSpaceChecker(mock_cache.path)); - - std::string file1_path = mock_cache.path + "/file1"s; - android::base::unique_fd fd(open(file1_path.c_str(), O_RDONLY)); - - // file1 can't be deleted as it's opened by us. - ASSERT_FALSE(RemoveFilesInDirectory(4096 * 10, mock_cache.path, MockFreeSpaceChecker)); - - ASSERT_EQ(std::vector{ "file1" }, FindFilesInDir(mock_cache.path)); -} - -TEST_F(FreeCacheTest, FreeCacheLogsSmoke) { - std::vector log_files = { "last_log", "last_log.1", "last_kmsg.2", "last_log.5", - "last_log.10" }; - AddFilesToDir(mock_log_dir.path, log_files); - ASSERT_EQ(4096 * 5, MockFreeSpaceChecker(mock_log_dir.path)); - - ASSERT_TRUE(RemoveFilesInDirectory(4096 * 8, mock_log_dir.path, MockFreeSpaceChecker)); - - // Logs with a higher index will be deleted first - std::vector expected = { "last_log", "last_log.1" }; - ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path)); - ASSERT_EQ(4096 * 8, MockFreeSpaceChecker(mock_log_dir.path)); -} - -TEST_F(FreeCacheTest, FreeCacheLogsStringComparison) { - std::vector log_files = { "last_log.1", "last_kmsg.1", "last_log.not_number", - "last_kmsgrandom" }; - AddFilesToDir(mock_log_dir.path, log_files); - ASSERT_EQ(4096 * 6, MockFreeSpaceChecker(mock_log_dir.path)); - - ASSERT_TRUE(RemoveFilesInDirectory(4096 * 9, mock_log_dir.path, MockFreeSpaceChecker)); - - // Logs with incorrect format will be deleted first; and the last_kmsg with the same index is - // deleted before last_log. - std::vector expected = { "last_log.1" }; - ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path)); - ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_log_dir.path)); -} - -TEST_F(FreeCacheTest, FreeCacheLogsOtherFiles) { - std::vector log_files = { "last_install", "command", "block.map", "last_log", - "last_kmsg.1" }; - AddFilesToDir(mock_log_dir.path, log_files); - ASSERT_EQ(4096 * 5, MockFreeSpaceChecker(mock_log_dir.path)); - - ASSERT_FALSE(RemoveFilesInDirectory(4096 * 8, mock_log_dir.path, MockFreeSpaceChecker)); - - // Non log files in /cache/recovery won't be deleted. - std::vector expected = { "block.map", "command", "last_install" }; - ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path)); -} diff --git a/tests/unit/commands_test.cpp b/tests/unit/commands_test.cpp deleted file mode 100644 index 8a54df70..00000000 --- a/tests/unit/commands_test.cpp +++ /dev/null @@ -1,554 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include - -#include -#include -#include - -#include "otautil/print_sha1.h" -#include "otautil/rangeset.h" -#include "private/commands.h" - -TEST(CommandsTest, ParseType) { - ASSERT_EQ(Command::Type::ZERO, Command::ParseType("zero")); - ASSERT_EQ(Command::Type::NEW, Command::ParseType("new")); - ASSERT_EQ(Command::Type::ERASE, Command::ParseType("erase")); - ASSERT_EQ(Command::Type::MOVE, Command::ParseType("move")); - ASSERT_EQ(Command::Type::BSDIFF, Command::ParseType("bsdiff")); - ASSERT_EQ(Command::Type::IMGDIFF, Command::ParseType("imgdiff")); - ASSERT_EQ(Command::Type::STASH, Command::ParseType("stash")); - ASSERT_EQ(Command::Type::FREE, Command::ParseType("free")); - ASSERT_EQ(Command::Type::COMPUTE_HASH_TREE, Command::ParseType("compute_hash_tree")); -} - -TEST(CommandsTest, ParseType_InvalidCommand) { - ASSERT_EQ(Command::Type::LAST, Command::ParseType("foo")); - ASSERT_EQ(Command::Type::LAST, Command::ParseType("bar")); -} - -TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly) { - const std::vector tokens{ - "4,569884,569904,591946,592043", - "117", - "4,566779,566799,591946,592043", - }; - TargetInfo target; - SourceInfo source; - std::string err; - ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo( - tokens, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target, - "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err)); - ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 569884, 569904 }, { 591946, 592043 } })), - target); - ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), {}, {}), - source); - ASSERT_EQ(117, source.blocks()); -} - -TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly) { - const std::vector tokens{ - "2,350729,350731", - "2", - "-", - "6ebcf8cf1f6be0bc49e7d4a864214251925d1d15:2,0,2", - }; - TargetInfo target; - SourceInfo source; - std::string err; - ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo( - tokens, "6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", &target, - "1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", &source, &err)); - ASSERT_EQ( - TargetInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 350729, 350731 } })), - target); - ASSERT_EQ( - SourceInfo("1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", {}, {}, - { - StashInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 0, 2 } })), - }), - source); - ASSERT_EQ(2, source.blocks()); -} - -TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes) { - const std::vector tokens{ - "4,611641,611643,636981,637075", - "96", - "4,636981,637075,770665,770666", - "4,0,94,95,96", - "9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95", - }; - TargetInfo target; - SourceInfo source; - std::string err; - ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo( - tokens, "4734d1b241eb3d0f993714aaf7d665fae43772b6", &target, - "a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", &source, &err)); - ASSERT_EQ(TargetInfo("4734d1b241eb3d0f993714aaf7d665fae43772b6", - RangeSet({ { 611641, 611643 }, { 636981, 637075 } })), - target); - ASSERT_EQ(SourceInfo( - "a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", - RangeSet({ { 636981, 637075 }, { 770665, 770666 } }), // source ranges - RangeSet({ { 0, 94 }, { 95, 96 } }), // source location - { - StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23", RangeSet({ { 94, 95 } })), - }), - source); - ASSERT_EQ(96, source.blocks()); -} - -TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput) { - const std::vector tokens{ - "4,611641,611643,636981,637075", - "96", - "4,636981,637075,770665,770666", - "4,0,94,95,96", - "9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95", - }; - TargetInfo target; - SourceInfo source; - std::string err; - - // Mismatching block count. - { - std::vector tokens_copy(tokens); - tokens_copy[1] = "97"; - ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo( - tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target, - "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err)); - } - - // Excess stashes (causing block count mismatch). - { - std::vector tokens_copy(tokens); - tokens_copy.push_back("e145a2f83a33334714ac65e34969c1f115e54a6f:2,0,22"); - ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo( - tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target, - "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err)); - } - - // Invalid args. - for (size_t i = 0; i < tokens.size(); i++) { - TargetInfo target; - SourceInfo source; - std::string err; - ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo( - std::vector(tokens.cbegin() + i + 1, tokens.cend()), - "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target, - "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err)); - } -} - -TEST(CommandsTest, Parse_EmptyInput) { - std::string err; - ASSERT_FALSE(Command::Parse("", 0, &err)); - ASSERT_EQ("invalid type", err); -} - -TEST(CommandsTest, Parse_ABORT_Allowed) { - Command::abort_allowed_ = true; - - const std::string input{ "abort" }; - std::string err; - Command command = Command::Parse(input, 0, &err); - ASSERT_TRUE(command); - - ASSERT_EQ(TargetInfo(), command.target()); - ASSERT_EQ(SourceInfo(), command.source()); - ASSERT_EQ(StashInfo(), command.stash()); - ASSERT_EQ(PatchInfo(), command.patch()); -} - -TEST(CommandsTest, Parse_ABORT_NotAllowed) { - const std::string input{ "abort" }; - std::string err; - Command command = Command::Parse(input, 0, &err); - ASSERT_FALSE(command); -} - -TEST(CommandsTest, Parse_BSDIFF) { - const std::string input{ - "bsdiff 0 148 " - "f201a4e04bd3860da6ad47b957ef424d58a58f8c 9d5d223b4bc5c45dbd25a799c4f1a98466731599 " - "4,565704,565752,566779,566799 " - "68 4,64525,64545,565704,565752" - }; - std::string err; - Command command = Command::Parse(input, 1, &err); - ASSERT_TRUE(command); - - ASSERT_EQ(Command::Type::BSDIFF, command.type()); - ASSERT_EQ(1, command.index()); - ASSERT_EQ(input, command.cmdline()); - - ASSERT_EQ(TargetInfo("9d5d223b4bc5c45dbd25a799c4f1a98466731599", - RangeSet({ { 565704, 565752 }, { 566779, 566799 } })), - command.target()); - ASSERT_EQ(SourceInfo("f201a4e04bd3860da6ad47b957ef424d58a58f8c", - RangeSet({ { 64525, 64545 }, { 565704, 565752 } }), RangeSet(), {}), - command.source()); - ASSERT_EQ(StashInfo(), command.stash()); - ASSERT_EQ(PatchInfo(0, 148), command.patch()); -} - -TEST(CommandsTest, Parse_ERASE) { - const std::string input{ "erase 2,5,10" }; - std::string err; - Command command = Command::Parse(input, 2, &err); - ASSERT_TRUE(command); - - ASSERT_EQ(Command::Type::ERASE, command.type()); - ASSERT_EQ(2, command.index()); - ASSERT_EQ(input, command.cmdline()); - - ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 5, 10 } })), command.target()); - ASSERT_EQ(SourceInfo(), command.source()); - ASSERT_EQ(StashInfo(), command.stash()); - ASSERT_EQ(PatchInfo(), command.patch()); -} - -TEST(CommandsTest, Parse_FREE) { - const std::string input{ "free hash1" }; - std::string err; - Command command = Command::Parse(input, 3, &err); - ASSERT_TRUE(command); - - ASSERT_EQ(Command::Type::FREE, command.type()); - ASSERT_EQ(3, command.index()); - ASSERT_EQ(input, command.cmdline()); - - ASSERT_EQ(TargetInfo(), command.target()); - ASSERT_EQ(SourceInfo(), command.source()); - ASSERT_EQ(StashInfo("hash1", RangeSet()), command.stash()); - ASSERT_EQ(PatchInfo(), command.patch()); -} - -TEST(CommandsTest, Parse_IMGDIFF) { - const std::string input{ - "imgdiff 29629269 185 " - "a6b1c49aed1b57a2aab1ec3e1505b945540cd8db 51978f65035f584a8ef7afa941dacb6d5e862164 " - "2,90851,90852 " - "1 2,90851,90852" - }; - std::string err; - Command command = Command::Parse(input, 4, &err); - ASSERT_TRUE(command); - - ASSERT_EQ(Command::Type::IMGDIFF, command.type()); - ASSERT_EQ(4, command.index()); - ASSERT_EQ(input, command.cmdline()); - - ASSERT_EQ(TargetInfo("51978f65035f584a8ef7afa941dacb6d5e862164", RangeSet({ { 90851, 90852 } })), - command.target()); - ASSERT_EQ(SourceInfo("a6b1c49aed1b57a2aab1ec3e1505b945540cd8db", RangeSet({ { 90851, 90852 } }), - RangeSet(), {}), - command.source()); - ASSERT_EQ(StashInfo(), command.stash()); - ASSERT_EQ(PatchInfo(29629269, 185), command.patch()); -} - -TEST(CommandsTest, Parse_MOVE) { - const std::string input{ - "move 1d74d1a60332fd38cf9405f1bae67917888da6cb " - "4,569884,569904,591946,592043 117 4,566779,566799,591946,592043" - }; - std::string err; - Command command = Command::Parse(input, 5, &err); - ASSERT_TRUE(command); - - ASSERT_EQ(Command::Type::MOVE, command.type()); - ASSERT_EQ(5, command.index()); - ASSERT_EQ(input, command.cmdline()); - - ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 569884, 569904 }, { 591946, 592043 } })), - command.target()); - ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), RangeSet(), {}), - command.source()); - ASSERT_EQ(StashInfo(), command.stash()); - ASSERT_EQ(PatchInfo(), command.patch()); -} - -TEST(CommandsTest, Parse_NEW) { - const std::string input{ "new 4,3,5,10,12" }; - std::string err; - Command command = Command::Parse(input, 6, &err); - ASSERT_TRUE(command); - - ASSERT_EQ(Command::Type::NEW, command.type()); - ASSERT_EQ(6, command.index()); - ASSERT_EQ(input, command.cmdline()); - - ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 3, 5 }, { 10, 12 } })), command.target()); - ASSERT_EQ(SourceInfo(), command.source()); - ASSERT_EQ(StashInfo(), command.stash()); - ASSERT_EQ(PatchInfo(), command.patch()); -} - -TEST(CommandsTest, Parse_STASH) { - const std::string input{ "stash hash1 2,5,10" }; - std::string err; - Command command = Command::Parse(input, 7, &err); - ASSERT_TRUE(command); - - ASSERT_EQ(Command::Type::STASH, command.type()); - ASSERT_EQ(7, command.index()); - ASSERT_EQ(input, command.cmdline()); - - ASSERT_EQ(TargetInfo(), command.target()); - ASSERT_EQ(SourceInfo(), command.source()); - ASSERT_EQ(StashInfo("hash1", RangeSet({ { 5, 10 } })), command.stash()); - ASSERT_EQ(PatchInfo(), command.patch()); -} - -TEST(CommandsTest, Parse_ZERO) { - const std::string input{ "zero 2,1,5" }; - std::string err; - Command command = Command::Parse(input, 8, &err); - ASSERT_TRUE(command); - - ASSERT_EQ(Command::Type::ZERO, command.type()); - ASSERT_EQ(8, command.index()); - ASSERT_EQ(input, command.cmdline()); - - ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 1, 5 } })), command.target()); - ASSERT_EQ(SourceInfo(), command.source()); - ASSERT_EQ(StashInfo(), command.stash()); - ASSERT_EQ(PatchInfo(), command.patch()); -} - -TEST(CommandsTest, Parse_COMPUTE_HASH_TREE) { - const std::string input{ "compute_hash_tree 2,0,1 2,3,4 sha1 unknown-salt unknown-root-hash" }; - std::string err; - Command command = Command::Parse(input, 9, &err); - ASSERT_TRUE(command); - - ASSERT_EQ(Command::Type::COMPUTE_HASH_TREE, command.type()); - ASSERT_EQ(9, command.index()); - ASSERT_EQ(input, command.cmdline()); - - HashTreeInfo expected_info(RangeSet({ { 0, 1 } }), RangeSet({ { 3, 4 } }), "sha1", "unknown-salt", - "unknown-root-hash"); - ASSERT_EQ(expected_info, command.hash_tree_info()); - ASSERT_EQ(TargetInfo(), command.target()); - ASSERT_EQ(SourceInfo(), command.source()); - ASSERT_EQ(StashInfo(), command.stash()); - ASSERT_EQ(PatchInfo(), command.patch()); -} - -TEST(CommandsTest, Parse_InvalidNumberOfArgs) { - Command::abort_allowed_ = true; - - // Note that the case of having excess args in BSDIFF, IMGDIFF and MOVE is covered by - // ParseTargetInfoAndSourceInfo_InvalidInput. - std::vector inputs{ - "abort foo", - "bsdiff", - "compute_hash_tree, 2,0,1 2,0,1 unknown-algorithm unknown-salt", - "erase", - "erase 4,3,5,10,12 hash1", - "free", - "free id1 id2", - "imgdiff", - "move", - "new", - "new 4,3,5,10,12 hash1", - "stash", - "stash id1", - "stash id1 4,3,5,10,12 id2", - "zero", - "zero 4,3,5,10,12 hash2", - }; - for (const auto& input : inputs) { - std::string err; - ASSERT_FALSE(Command::Parse(input, 0, &err)); - } -} - -TEST(SourceInfoTest, Overlaps) { - ASSERT_TRUE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {}) - .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 7, 9 }, { 16, 20 } })))); - - ASSERT_TRUE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {}) - .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 4, 7 }, { 16, 23 } })))); - - ASSERT_FALSE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {}) - .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 9, 16 } })))); -} - -TEST(SourceInfoTest, Overlaps_EmptySourceOrTarget) { - ASSERT_FALSE(SourceInfo().Overlaps(TargetInfo())); - - ASSERT_FALSE(SourceInfo().Overlaps( - TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", RangeSet({ { 7, 9 }, { 16, 20 } })))); - - ASSERT_FALSE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {}) - .Overlaps(TargetInfo())); -} - -TEST(SourceInfoTest, Overlaps_WithStashes) { - ASSERT_FALSE(SourceInfo("a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", - RangeSet({ { 81, 175 }, { 265, 266 } }), // source ranges - RangeSet({ { 0, 94 }, { 95, 96 } }), // source location - { StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23", - RangeSet({ { 94, 95 } })) }) - .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 175, 265 } })))); - - ASSERT_TRUE(SourceInfo("a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", - RangeSet({ { 81, 175 }, { 265, 266 } }), // source ranges - RangeSet({ { 0, 94 }, { 95, 96 } }), // source location - { StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23", - RangeSet({ { 94, 95 } })) }) - .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", - RangeSet({ { 265, 266 } })))); -} - -// The block size should be specified by the caller of ReadAll (i.e. from Command instance during -// normal run). -constexpr size_t kBlockSize = 4096; - -TEST(SourceInfoTest, ReadAll) { - // "2727756cfee3fbfe24bf5650123fd7743d7b3465" is the SHA-1 hex digest of 8192 * 'a'. - const SourceInfo source("2727756cfee3fbfe24bf5650123fd7743d7b3465", RangeSet({ { 0, 2 } }), {}, - {}); - auto block_reader = [](const RangeSet& src, std::vector* block_buffer) -> int { - std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a'); - return 0; - }; - auto stash_reader = [](const std::string&, std::vector*) -> int { return 0; }; - std::vector buffer(source.blocks() * kBlockSize); - ASSERT_TRUE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader)); - ASSERT_EQ(source.blocks() * kBlockSize, buffer.size()); - - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(buffer.data(), buffer.size(), digest); - ASSERT_EQ(source.hash(), print_sha1(digest)); -} - -TEST(SourceInfoTest, ReadAll_WithStashes) { - const SourceInfo source( - // SHA-1 hex digest of 8192 * 'a' + 4096 * 'b'. - "ee3ebea26130769c10ad13604712100346d48660", RangeSet({ { 0, 2 } }), RangeSet({ { 0, 2 } }), - { StashInfo("1e41f7a59e80c6eb4dc043caae80d273f130bed8", RangeSet({ { 2, 3 } })) }); - auto block_reader = [](const RangeSet& src, std::vector* block_buffer) -> int { - std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a'); - return 0; - }; - auto stash_reader = [](const std::string&, std::vector* stash_buffer) -> int { - std::fill_n(stash_buffer->begin(), kBlockSize, 'b'); - return 0; - }; - std::vector buffer(source.blocks() * kBlockSize); - ASSERT_TRUE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader)); - ASSERT_EQ(source.blocks() * kBlockSize, buffer.size()); - - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(buffer.data(), buffer.size(), digest); - ASSERT_EQ(source.hash(), print_sha1(digest)); -} - -TEST(SourceInfoTest, ReadAll_BufferTooSmall) { - const SourceInfo source("2727756cfee3fbfe24bf5650123fd7743d7b3465", RangeSet({ { 0, 2 } }), {}, - {}); - auto block_reader = [](const RangeSet&, std::vector*) -> int { return 0; }; - auto stash_reader = [](const std::string&, std::vector*) -> int { return 0; }; - std::vector buffer(source.blocks() * kBlockSize - 1); - ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader)); -} - -TEST(SourceInfoTest, ReadAll_FailingReader) { - const SourceInfo source( - "ee3ebea26130769c10ad13604712100346d48660", RangeSet({ { 0, 2 } }), RangeSet({ { 0, 2 } }), - { StashInfo("1e41f7a59e80c6eb4dc043caae80d273f130bed8", RangeSet({ { 2, 3 } })) }); - std::vector buffer(source.blocks() * kBlockSize); - auto failing_block_reader = [](const RangeSet&, std::vector*) -> int { return -1; }; - auto stash_reader = [](const std::string&, std::vector*) -> int { return 0; }; - ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, failing_block_reader, stash_reader)); - - auto block_reader = [](const RangeSet&, std::vector*) -> int { return 0; }; - auto failing_stash_reader = [](const std::string&, std::vector*) -> int { return -1; }; - ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, failing_stash_reader)); -} - -TEST(TransferListTest, Parse) { - std::vector input_lines{ - "4", // version - "2", // total blocks - "1", // max stashed entries - "1", // max stashed blocks - "stash 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1", - "move 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1 1 2,0,1", - }; - - std::string err; - TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err); - ASSERT_TRUE(static_cast(transfer_list)); - ASSERT_EQ(4, transfer_list.version()); - ASSERT_EQ(2, transfer_list.total_blocks()); - ASSERT_EQ(1, transfer_list.stash_max_entries()); - ASSERT_EQ(1, transfer_list.stash_max_blocks()); - ASSERT_EQ(2U, transfer_list.commands().size()); - ASSERT_EQ(Command::Type::STASH, transfer_list.commands()[0].type()); - ASSERT_EQ(Command::Type::MOVE, transfer_list.commands()[1].type()); -} - -TEST(TransferListTest, Parse_InvalidCommand) { - std::vector input_lines{ - "4", // version - "2", // total blocks - "1", // max stashed entries - "1", // max stashed blocks - "stash 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1", - "move 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1 1", - }; - - std::string err; - TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err); - ASSERT_FALSE(static_cast(transfer_list)); -} - -TEST(TransferListTest, Parse_ZeroTotalBlocks) { - std::vector input_lines{ - "4", // version - "0", // total blocks - "0", // max stashed entries - "0", // max stashed blocks - }; - - std::string err; - TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err); - ASSERT_TRUE(static_cast(transfer_list)); - ASSERT_EQ(4, transfer_list.version()); - ASSERT_EQ(0, transfer_list.total_blocks()); - ASSERT_EQ(0, transfer_list.stash_max_entries()); - ASSERT_EQ(0, transfer_list.stash_max_blocks()); - ASSERT_TRUE(transfer_list.commands().empty()); -} diff --git a/tests/unit/edify_test.cpp b/tests/unit/edify_test.cpp deleted file mode 100644 index 8397bd38..00000000 --- a/tests/unit/edify_test.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include - -#include - -#include "edify/expr.h" - -static void expect(const std::string& expr_str, const char* expected) { - std::unique_ptr e; - int error_count = 0; - EXPECT_EQ(0, ParseString(expr_str, &e, &error_count)); - EXPECT_EQ(0, error_count); - - State state(expr_str, nullptr); - - std::string result; - bool status = Evaluate(&state, e, &result); - - if (expected == nullptr) { - EXPECT_FALSE(status); - } else { - EXPECT_STREQ(expected, result.c_str()); - } -} - -class EdifyTest : public ::testing::Test { - protected: - void SetUp() { - RegisterBuiltins(); - } -}; - -TEST_F(EdifyTest, parsing) { - expect("a", "a"); - expect("\"a\"", "a"); - expect("\"\\x61\"", "a"); - expect("# this is a comment\n" - " a\n" - " \n", - "a"); -} - -TEST_F(EdifyTest, sequence) { - // sequence operator - expect("a; b; c", "c"); -} - -TEST_F(EdifyTest, concat) { - // string concat operator - expect("a + b", "ab"); - expect("a + \n \"b\"", "ab"); - expect("a + b +\nc\n", "abc"); - - // string concat function - expect("concat(a, b)", "ab"); - expect("concat(a,\n \"b\")", "ab"); - expect("concat(a + b,\nc,\"d\")", "abcd"); - expect("\"concat\"(a + b,\nc,\"d\")", "abcd"); -} - -TEST_F(EdifyTest, logical) { - // logical and - expect("a && b", "b"); - expect("a && \"\"", ""); - expect("\"\" && b", ""); - expect("\"\" && \"\"", ""); - expect("\"\" && abort()", ""); // test short-circuiting - expect("t && abort()", nullptr); - - // logical or - expect("a || b", "a"); - expect("a || \"\"", "a"); - expect("\"\" || b", "b"); - expect("\"\" || \"\"", ""); - expect("a || abort()", "a"); // test short-circuiting - expect("\"\" || abort()", NULL); - - // logical not - expect("!a", ""); - expect("! \"\"", "t"); - expect("!!a", "t"); -} - -TEST_F(EdifyTest, precedence) { - // precedence - expect("\"\" == \"\" && b", "b"); - expect("a + b == ab", "t"); - expect("ab == a + b", "t"); - expect("a + (b == ab)", "a"); - expect("(ab == a) + b", "b"); -} - -TEST_F(EdifyTest, substring) { - // substring function - expect("is_substring(cad, abracadabra)", "t"); - expect("is_substring(abrac, abracadabra)", "t"); - expect("is_substring(dabra, abracadabra)", "t"); - expect("is_substring(cad, abracxadabra)", ""); - expect("is_substring(abrac, axbracadabra)", ""); - expect("is_substring(dabra, abracadabrxa)", ""); -} - -TEST_F(EdifyTest, ifelse) { - // ifelse function - expect("ifelse(t, yes, no)", "yes"); - expect("ifelse(!t, yes, no)", "no"); - expect("ifelse(t, yes, abort())", "yes"); - expect("ifelse(!t, abort(), no)", "no"); -} - -TEST_F(EdifyTest, if_statement) { - // if "statements" - expect("if t then yes else no endif", "yes"); - expect("if \"\" then yes else no endif", "no"); - expect("if \"\" then yes endif", ""); - expect("if \"\"; t then yes endif", "yes"); -} - -TEST_F(EdifyTest, comparison) { - // numeric comparisons - expect("less_than_int(3, 14)", "t"); - expect("less_than_int(14, 3)", ""); - expect("less_than_int(x, 3)", ""); - expect("less_than_int(3, x)", ""); - expect("greater_than_int(3, 14)", ""); - expect("greater_than_int(14, 3)", "t"); - expect("greater_than_int(x, 3)", ""); - expect("greater_than_int(3, x)", ""); -} - -TEST_F(EdifyTest, big_string) { - expect(std::string(8192, 's'), std::string(8192, 's').c_str()); -} - -TEST_F(EdifyTest, unknown_function) { - const char* script1 = "unknown_function()"; - std::unique_ptr expr; - int error_count = 0; - EXPECT_EQ(1, ParseString(script1, &expr, &error_count)); - EXPECT_EQ(1, error_count); - - const char* script2 = "abc; unknown_function()"; - error_count = 0; - EXPECT_EQ(1, ParseString(script2, &expr, &error_count)); - EXPECT_EQ(1, error_count); - - const char* script3 = "unknown_function1() || yes"; - error_count = 0; - EXPECT_EQ(1, ParseString(script3, &expr, &error_count)); - EXPECT_EQ(1, error_count); -} diff --git a/tests/unit/host/imgdiff_test.cpp b/tests/unit/host/imgdiff_test.cpp deleted file mode 100644 index 978ac7c2..00000000 --- a/tests/unit/host/imgdiff_test.cpp +++ /dev/null @@ -1,1113 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common/test_constants.h" - -using android::base::get_unaligned; - -static void verify_patch_header(const std::string& patch, size_t* num_normal, size_t* num_raw, - size_t* num_deflate) { - const size_t size = patch.size(); - const char* data = patch.data(); - - ASSERT_GE(size, 12U); - ASSERT_EQ("IMGDIFF2", std::string(data, 8)); - - const int num_chunks = get_unaligned(data + 8); - ASSERT_GE(num_chunks, 0); - - size_t normal = 0; - size_t raw = 0; - size_t deflate = 0; - - size_t pos = 12; - for (int i = 0; i < num_chunks; ++i) { - ASSERT_LE(pos + 4, size); - int type = get_unaligned(data + pos); - pos += 4; - if (type == CHUNK_NORMAL) { - pos += 24; - ASSERT_LE(pos, size); - normal++; - } else if (type == CHUNK_RAW) { - ASSERT_LE(pos + 4, size); - ssize_t data_len = get_unaligned(data + pos); - ASSERT_GT(data_len, 0); - pos += 4 + data_len; - ASSERT_LE(pos, size); - raw++; - } else if (type == CHUNK_DEFLATE) { - pos += 60; - ASSERT_LE(pos, size); - deflate++; - } else { - FAIL() << "Invalid patch type: " << type; - } - } - - if (num_normal != nullptr) *num_normal = normal; - if (num_raw != nullptr) *num_raw = raw; - if (num_deflate != nullptr) *num_deflate = deflate; -} - -static void GenerateTarget(const std::string& src, const std::string& patch, std::string* patched) { - patched->clear(); - ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast(src.data()), src.size(), - reinterpret_cast(patch.data()), patch.size(), - [&](const unsigned char* data, size_t len) { - patched->append(reinterpret_cast(data), len); - return len; - })); -} - -static void verify_patched_image(const std::string& src, const std::string& patch, - const std::string& tgt) { - std::string patched; - GenerateTarget(src, patch, &patched); - ASSERT_EQ(tgt, patched); -} - -TEST(ImgdiffTest, invalid_args) { - // Insufficient inputs. - ASSERT_EQ(2, imgdiff(1, (const char* []){ "imgdiff" })); - ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-z" })); - ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-b" })); - ASSERT_EQ(2, imgdiff(3, (const char* []){ "imgdiff", "-z", "-b" })); - - // Failed to read bonus file. - ASSERT_EQ(1, imgdiff(3, (const char* []){ "imgdiff", "-b", "doesntexist" })); - - // Failed to read input files. - ASSERT_EQ(1, imgdiff(4, (const char* []){ "imgdiff", "doesntexist", "doesntexist", "output" })); - ASSERT_EQ( - 1, imgdiff(5, (const char* []){ "imgdiff", "-z", "doesntexist", "doesntexist", "output" })); -} - -TEST(ImgdiffTest, image_mode_smoke) { - // Random bytes. - const std::string src("abcdefg"); - TemporaryFile src_file; - ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); - - const std::string tgt("abcdefgxyz"); - TemporaryFile tgt_file; - ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); - - TemporaryFile patch_file; - std::vector 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)); - - // Expect one CHUNK_RAW entry. - size_t num_normal; - size_t num_raw; - size_t num_deflate; - verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); - ASSERT_EQ(0U, num_normal); - ASSERT_EQ(0U, num_deflate); - ASSERT_EQ(1U, num_raw); - - verify_patched_image(src, patch, tgt); -} - -TEST(ImgdiffTest, zip_mode_smoke_store) { - // Construct src and tgt zip files. - TemporaryFile src_file; - FILE* src_file_ptr = fdopen(src_file.release(), "wb"); - ZipWriter src_writer(src_file_ptr); - ASSERT_EQ(0, src_writer.StartEntry("file1.txt", 0)); // Store mode. - const std::string src_content("abcdefg"); - ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size())); - ASSERT_EQ(0, src_writer.FinishEntry()); - ASSERT_EQ(0, src_writer.Finish()); - ASSERT_EQ(0, fclose(src_file_ptr)); - - TemporaryFile tgt_file; - FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb"); - ZipWriter tgt_writer(tgt_file_ptr); - ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", 0)); // Store mode. - const std::string tgt_content("abcdefgxyz"); - ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size())); - ASSERT_EQ(0, tgt_writer.FinishEntry()); - ASSERT_EQ(0, tgt_writer.Finish()); - ASSERT_EQ(0, fclose(tgt_file_ptr)); - - // Compute patch. - TemporaryFile patch_file; - std::vector args = { - "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path, - }; - ASSERT_EQ(0, imgdiff(args.size(), args.data())); - - // Verify. - std::string tgt; - ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); - std::string src; - ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src)); - std::string patch; - ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); - - // Expect one CHUNK_RAW entry. - size_t num_normal; - size_t num_raw; - size_t num_deflate; - verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); - ASSERT_EQ(0U, num_normal); - ASSERT_EQ(0U, num_deflate); - ASSERT_EQ(1U, num_raw); - - verify_patched_image(src, patch, tgt); -} - -TEST(ImgdiffTest, zip_mode_smoke_compressed) { - // Generate 1 block of random data. - std::string random_data; - random_data.reserve(4096); - generate_n(back_inserter(random_data), 4096, []() { return rand() % 256; }); - - // Construct src and tgt zip files. - TemporaryFile src_file; - FILE* src_file_ptr = fdopen(src_file.release(), "wb"); - ZipWriter src_writer(src_file_ptr); - ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress)); - const std::string src_content = random_data; - ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size())); - ASSERT_EQ(0, src_writer.FinishEntry()); - ASSERT_EQ(0, src_writer.Finish()); - ASSERT_EQ(0, fclose(src_file_ptr)); - - TemporaryFile tgt_file; - FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb"); - ZipWriter tgt_writer(tgt_file_ptr); - ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress)); - const std::string tgt_content = random_data + "extra contents"; - ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size())); - ASSERT_EQ(0, tgt_writer.FinishEntry()); - ASSERT_EQ(0, tgt_writer.Finish()); - ASSERT_EQ(0, fclose(tgt_file_ptr)); - - // Compute patch. - TemporaryFile patch_file; - std::vector args = { - "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path, - }; - ASSERT_EQ(0, imgdiff(args.size(), args.data())); - - // Verify. - std::string tgt; - ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); - std::string src; - ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src)); - std::string patch; - ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); - - // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer). - size_t num_normal; - size_t num_raw; - size_t num_deflate; - verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); - ASSERT_EQ(0U, num_normal); - ASSERT_EQ(1U, num_deflate); - ASSERT_EQ(2U, num_raw); - - verify_patched_image(src, patch, tgt); -} - -TEST(ImgdiffTest, zip_mode_empty_target) { - TemporaryFile src_file; - FILE* src_file_ptr = fdopen(src_file.release(), "wb"); - ZipWriter src_writer(src_file_ptr); - ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress)); - const std::string src_content = "abcdefg"; - ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size())); - ASSERT_EQ(0, src_writer.FinishEntry()); - ASSERT_EQ(0, src_writer.Finish()); - ASSERT_EQ(0, fclose(src_file_ptr)); - - // Construct a empty entry in the target zip. - TemporaryFile tgt_file; - FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb"); - ZipWriter tgt_writer(tgt_file_ptr); - ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress)); - const std::string tgt_content; - ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size())); - ASSERT_EQ(0, tgt_writer.FinishEntry()); - ASSERT_EQ(0, tgt_writer.Finish()); - - // Compute patch. - TemporaryFile patch_file; - std::vector args = { - "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path, - }; - ASSERT_EQ(0, imgdiff(args.size(), args.data())); - - // Verify. - std::string tgt; - ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); - std::string src; - ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src)); - std::string patch; - ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); - - verify_patched_image(src, patch, tgt); -} - -TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) { - // Generate 1 block of random data. - std::string random_data; - random_data.reserve(4096); - generate_n(back_inserter(random_data), 4096, []() { return rand() % 256; }); - - // Construct src and tgt zip files. - TemporaryFile src_file; - FILE* src_file_ptr = fdopen(src_file.release(), "wb"); - ZipWriter src_writer(src_file_ptr); - ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress)); - const std::string src_content = random_data; - ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size())); - ASSERT_EQ(0, src_writer.FinishEntry()); - ASSERT_EQ(0, src_writer.Finish()); - ASSERT_EQ(0, fclose(src_file_ptr)); - - TemporaryFile tgt_file; - FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb"); - ZipWriter tgt_writer(tgt_file_ptr); - ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress)); - const std::string tgt_content = random_data + "abcdefg"; - ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size())); - ASSERT_EQ(0, tgt_writer.FinishEntry()); - ASSERT_EQ(0, tgt_writer.Finish()); - // Add trailing zeros to the target zip file. - std::vector zeros(10); - ASSERT_EQ(zeros.size(), fwrite(zeros.data(), sizeof(uint8_t), zeros.size(), tgt_file_ptr)); - ASSERT_EQ(0, fclose(tgt_file_ptr)); - - // Compute patch. - TemporaryFile patch_file; - std::vector args = { - "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path, - }; - ASSERT_EQ(0, imgdiff(args.size(), args.data())); - - // Verify. - std::string tgt; - ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); - std::string src; - ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src)); - std::string patch; - ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch)); - - // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer). - size_t num_normal; - size_t num_raw; - size_t num_deflate; - verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); - ASSERT_EQ(0U, num_normal); - ASSERT_EQ(1U, num_deflate); - ASSERT_EQ(2U, num_raw); - - verify_patched_image(src, patch, tgt); -} - -TEST(ImgdiffTest, image_mode_simple) { - std::string gzipped_source_path = from_testdata_base("gzipped_source"); - std::string gzipped_source; - ASSERT_TRUE(android::base::ReadFileToString(gzipped_source_path, &gzipped_source)); - - const std::string src = "abcdefg" + gzipped_source; - TemporaryFile src_file; - ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); - - std::string gzipped_target_path = from_testdata_base("gzipped_target"); - std::string gzipped_target; - ASSERT_TRUE(android::base::ReadFileToString(gzipped_target_path, &gzipped_target)); - const std::string tgt = "abcdefgxyz" + gzipped_target; - - TemporaryFile tgt_file; - ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); - - TemporaryFile patch_file; - std::vector 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)); - - // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer). - size_t num_normal; - size_t num_raw; - size_t num_deflate; - verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); - ASSERT_EQ(0U, num_normal); - ASSERT_EQ(1U, num_deflate); - ASSERT_EQ(2U, num_raw); - - verify_patched_image(src, patch, tgt); -} - -TEST(ImgdiffTest, image_mode_bad_gzip) { - // Modify the uncompressed length in the gzip footer. - const std::vector 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 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 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 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', '\x00', '\x00', '\x00', '\x1f', '\x8b', - '\x08', '\x00', '\xb2', '\x3a', '\x53', '\x58', '\x00', '\x03', '\x2b', '\x49', '\x2d', - '\x2e', '\x01', '\x00', '\x0c', '\x7e', '\x7f', '\xd8', '\x04', '\x00', '\x00', '\x00' - }; - const std::string src(src_data.cbegin(), src_data.cend()); - TemporaryFile src_file; - ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); - - // tgt: "abcdefgxyz" + gzipped "xxyyzz". - const std::vector 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', '\x00', '\x00', '\x00' - }; - 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 args = { - "imgdiff", src_file.path, tgt_file.path, patch_file.path, - }; - ASSERT_EQ(1, imgdiff(args.size(), args.data())); -} - -TEST(ImgdiffTest, image_mode_merge_chunks) { - // src: "abcdefg" + gzipped_source. - std::string gzipped_source_path = from_testdata_base("gzipped_source"); - std::string gzipped_source; - ASSERT_TRUE(android::base::ReadFileToString(gzipped_source_path, &gzipped_source)); - - const std::string src = "abcdefg" + gzipped_source; - TemporaryFile src_file; - ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); - - // tgt: gzipped_target + "abcdefgxyz". - std::string gzipped_target_path = from_testdata_base("gzipped_target"); - std::string gzipped_target; - ASSERT_TRUE(android::base::ReadFileToString(gzipped_target_path, &gzipped_target)); - - const std::string tgt = gzipped_target + "abcdefgxyz"; - TemporaryFile tgt_file; - ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path)); - - // Since a gzipped entry will become CHUNK_RAW (header) + CHUNK_DEFLATE (data) + - // CHUNK_RAW (footer), they both should contain the same chunk types after merging. - - TemporaryFile patch_file; - std::vector 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)); - - // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer). - size_t num_normal; - size_t num_raw; - size_t num_deflate; - verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); - ASSERT_EQ(0U, num_normal); - ASSERT_EQ(1U, num_deflate); - ASSERT_EQ(2U, num_raw); - - verify_patched_image(src, patch, tgt); -} - -TEST(ImgdiffTest, image_mode_spurious_magic) { - // src: "abcdefgh" + '0x1f8b0b00' + some bytes. - const std::vector src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', - 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e', - '\x53', '\x58', 't', 'e', 's', 't' }; - const std::string src(src_data.cbegin(), src_data.cend()); - TemporaryFile src_file; - ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); - - // tgt: "abcdefgxyz". - const std::vector tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' }; - 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 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)); - - // Expect one CHUNK_RAW (header) entry. - size_t num_normal; - size_t num_raw; - size_t num_deflate; - verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); - ASSERT_EQ(0U, num_normal); - ASSERT_EQ(0U, num_deflate); - ASSERT_EQ(1U, num_raw); - - verify_patched_image(src, patch, tgt); -} - -TEST(ImgdiffTest, image_mode_short_input1) { - // src: "abcdefgh" + '0x1f8b0b'. - const std::vector src_data = { 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', '\x1f', '\x8b', '\x08' }; - const std::string src(src_data.cbegin(), src_data.cend()); - TemporaryFile src_file; - ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); - - // tgt: "abcdefgxyz". - const std::vector tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' }; - 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 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)); - - // Expect one CHUNK_RAW (header) entry. - size_t num_normal; - size_t num_raw; - size_t num_deflate; - verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); - ASSERT_EQ(0U, num_normal); - ASSERT_EQ(0U, num_deflate); - ASSERT_EQ(1U, num_raw); - - verify_patched_image(src, patch, tgt); -} - -TEST(ImgdiffTest, image_mode_short_input2) { - // src: "abcdefgh" + '0x1f8b0b00'. - const std::vector src_data = { 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', '\x1f', '\x8b', '\x08', '\x00' }; - const std::string src(src_data.cbegin(), src_data.cend()); - TemporaryFile src_file; - ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); - - // tgt: "abcdefgxyz". - const std::vector tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' }; - 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 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)); - - // Expect one CHUNK_RAW (header) entry. - size_t num_normal; - size_t num_raw; - size_t num_deflate; - verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); - ASSERT_EQ(0U, num_normal); - ASSERT_EQ(0U, num_deflate); - ASSERT_EQ(1U, num_raw); - - verify_patched_image(src, patch, tgt); -} - -TEST(ImgdiffTest, image_mode_single_entry_long) { - // src: "abcdefgh" + '0x1f8b0b00' + some bytes. - const std::vector src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', - 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e', - '\x53', '\x58', 't', 'e', 's', 't' }; - const std::string src(src_data.cbegin(), src_data.cend()); - TemporaryFile src_file; - ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); - - // tgt: "abcdefgxyz" + 200 bytes. - std::vector tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' }; - tgt_data.resize(tgt_data.size() + 200); - - 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 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)); - - // Expect one CHUNK_NORMAL entry, since it's exceeding the 160-byte limit for RAW. - size_t num_normal; - size_t num_raw; - size_t num_deflate; - verify_patch_header(patch, &num_normal, &num_raw, &num_deflate); - ASSERT_EQ(1U, num_normal); - ASSERT_EQ(0U, num_deflate); - ASSERT_EQ(0U, num_raw); - - verify_patched_image(src, patch, tgt); -} - -TEST(ImgpatchTest, image_mode_patch_corruption) { - // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd). - const std::vector 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', - '\x00', '\x00', '\x00' }; - const std::string src(src_data.cbegin(), src_data.cend()); - TemporaryFile src_file; - ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path)); - - // tgt: "abcdefgxyz" + gzipped "xxyyzz". - const std::vector 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', '\x00', '\x00', '\x00' - }; - 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 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); - - // Corrupt the end of the patch and expect the ApplyImagePatch to fail. - patch.insert(patch.end() - 10, 10, '0'); - ASSERT_EQ(-1, ApplyImagePatch(reinterpret_cast(src.data()), src.size(), - reinterpret_cast(patch.data()), patch.size(), - [](const unsigned char* /*data*/, size_t len) { return len; })); -} - -static void construct_store_entry(const std::vector>& info, - ZipWriter* writer) { - for (auto& t : info) { - // Create t(1) blocks of t(2), and write the data to t(0) - ASSERT_EQ(0, writer->StartEntry(std::get<0>(t).c_str(), 0)); - const std::string content(std::get<1>(t) * 4096, std::get<2>(t)); - ASSERT_EQ(0, writer->WriteBytes(content.data(), content.size())); - ASSERT_EQ(0, writer->FinishEntry()); - } -} - -static void construct_deflate_entry(const std::vector>& info, - ZipWriter* writer, const std::string& data) { - for (auto& t : info) { - // t(0): entry_name; t(1): block offset; t(2) length in blocks. - ASSERT_EQ(0, writer->StartEntry(std::get<0>(t).c_str(), ZipWriter::kCompress)); - ASSERT_EQ(0, writer->WriteBytes(data.data() + std::get<1>(t) * 4096, std::get<2>(t) * 4096)); - ASSERT_EQ(0, writer->FinishEntry()); - } -} - -// Look for the source and patch pieces in debug_dir. Generate a target piece from each pair. -// Concatenate all the target pieces and match against the orignal one. Used pieces in debug_dir -// will be cleaned up. -static void GenerateAndCheckSplitTarget(const std::string& debug_dir, size_t count, - const std::string& tgt) { - std::string patched; - for (size_t i = 0; i < count; i++) { - std::string split_src_path = android::base::StringPrintf("%s/src-%zu", debug_dir.c_str(), i); - std::string split_src; - ASSERT_TRUE(android::base::ReadFileToString(split_src_path, &split_src)); - ASSERT_EQ(0, unlink(split_src_path.c_str())); - - std::string split_patch_path = - android::base::StringPrintf("%s/patch-%zu", debug_dir.c_str(), i); - std::string split_patch; - ASSERT_TRUE(android::base::ReadFileToString(split_patch_path, &split_patch)); - ASSERT_EQ(0, unlink(split_patch_path.c_str())); - - std::string split_tgt; - GenerateTarget(split_src, split_patch, &split_tgt); - patched += split_tgt; - } - - // Verify we can get back the original target image. - ASSERT_EQ(tgt, patched); -} - -std::vector ConstructImageChunks( - const std::vector& content, const std::vector>& info) { - std::vector chunks; - size_t start = 0; - for (const auto& t : info) { - size_t length = std::get<1>(t); - chunks.emplace_back(CHUNK_NORMAL, start, &content, length, std::get<0>(t)); - start += length; - } - - return chunks; -} - -TEST(ImgdiffTest, zip_mode_split_image_smoke) { - std::vector content; - content.reserve(4096 * 50); - uint8_t n = 0; - generate_n(back_inserter(content), 4096 * 50, [&n]() { return n++ / 4096; }); - - ZipModeImage tgt_image(false, 4096 * 10); - std::vector tgt_chunks = ConstructImageChunks(content, { { "a", 100 }, - { "b", 4096 * 2 }, - { "c", 4096 * 3 }, - { "d", 300 }, - { "e-0", 4096 * 10 }, - { "e-1", 4096 * 5 }, - { "CD", 200 } }); - tgt_image.Initialize(std::move(tgt_chunks), - std::vector(content.begin(), content.begin() + 82520)); - - tgt_image.DumpChunks(); - - ZipModeImage src_image(true, 4096 * 10); - std::vector src_chunks = ConstructImageChunks(content, { { "b", 4096 * 3 }, - { "c-0", 4096 * 10 }, - { "c-1", 4096 * 2 }, - { "a", 4096 * 5 }, - { "e-0", 4096 * 10 }, - { "e-1", 10000 }, - { "CD", 5000 } }); - src_image.Initialize(std::move(src_chunks), - std::vector(content.begin(), content.begin() + 137880)); - - std::vector split_tgt_images; - std::vector split_src_images; - std::vector split_src_ranges; - - ZipModeImage::SplitZipModeImageWithLimit(tgt_image, src_image, &split_tgt_images, - &split_src_images, &split_src_ranges); - - // src_piece 1: a 5 blocks, b 3 blocks - // src_piece 2: c-0 10 blocks - // src_piece 3: d 0 block, e-0 10 blocks - // src_piece 4: e-1 2 blocks; CD 2 blocks - ASSERT_EQ(split_tgt_images.size(), split_src_images.size()); - ASSERT_EQ(static_cast(4), split_tgt_images.size()); - - ASSERT_EQ(static_cast(1), split_tgt_images[0].NumOfChunks()); - ASSERT_EQ(static_cast(12288), split_tgt_images[0][0].DataLengthForPatch()); - ASSERT_EQ("4,0,3,15,20", split_src_ranges[0].ToString()); - - ASSERT_EQ(static_cast(1), split_tgt_images[1].NumOfChunks()); - ASSERT_EQ(static_cast(12288), split_tgt_images[1][0].DataLengthForPatch()); - ASSERT_EQ("2,3,13", split_src_ranges[1].ToString()); - - ASSERT_EQ(static_cast(1), split_tgt_images[2].NumOfChunks()); - ASSERT_EQ(static_cast(40960), split_tgt_images[2][0].DataLengthForPatch()); - ASSERT_EQ("2,20,30", split_src_ranges[2].ToString()); - - ASSERT_EQ(static_cast(1), split_tgt_images[3].NumOfChunks()); - ASSERT_EQ(static_cast(16984), split_tgt_images[3][0].DataLengthForPatch()); - ASSERT_EQ("2,30,34", split_src_ranges[3].ToString()); -} - -TEST(ImgdiffTest, zip_mode_store_large_apk) { - // Construct src and tgt zip files with limit = 10 blocks. - // src tgt - // 12 blocks 'd' 3 blocks 'a' - // 8 blocks 'c' 3 blocks 'b' - // 3 blocks 'b' 8 blocks 'c' (exceeds limit) - // 3 blocks 'a' 12 blocks 'd' (exceeds limit) - // 3 blocks 'e' - TemporaryFile tgt_file; - FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb"); - ZipWriter tgt_writer(tgt_file_ptr); - construct_store_entry( - { { "a", 3, 'a' }, { "b", 3, 'b' }, { "c", 8, 'c' }, { "d", 12, 'd' }, { "e", 3, 'e' } }, - &tgt_writer); - ASSERT_EQ(0, tgt_writer.Finish()); - ASSERT_EQ(0, fclose(tgt_file_ptr)); - - TemporaryFile src_file; - FILE* src_file_ptr = fdopen(src_file.release(), "wb"); - ZipWriter src_writer(src_file_ptr); - construct_store_entry({ { "d", 12, 'd' }, { "c", 8, 'c' }, { "b", 3, 'b' }, { "a", 3, 'a' } }, - &src_writer); - ASSERT_EQ(0, src_writer.Finish()); - ASSERT_EQ(0, fclose(src_file_ptr)); - - // Compute patch. - TemporaryFile patch_file; - TemporaryFile split_info_file; - TemporaryDir debug_dir; - std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path); - std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path); - std::vector args = { - "imgdiff", "-z", "--block-limit=10", split_info_arg.c_str(), debug_dir_arg.c_str(), - src_file.path, tgt_file.path, patch_file.path, - }; - ASSERT_EQ(0, imgdiff(args.size(), args.data())); - - std::string tgt; - ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); - - // Expect 4 pieces of patch. (Roughly 3'a',3'b'; 8'c'; 10'd'; 2'd'3'e') - GenerateAndCheckSplitTarget(debug_dir.path, 4, tgt); -} - -TEST(ImgdiffTest, zip_mode_deflate_large_apk) { - // Src and tgt zip files are constructed as follows. - // src tgt - // 22 blocks, "d" 4 blocks, "a" - // 5 blocks, "b" 4 blocks, "b" - // 3 blocks, "a" 8 blocks, "c" (exceeds limit) - // 1 block, "g" 20 blocks, "d" (exceeds limit) - // 8 blocks, "c" 2 blocks, "e" - // 1 block, "f" 1 block , "f" - std::string tgt_path = from_testdata_base("deflate_tgt.zip"); - std::string src_path = from_testdata_base("deflate_src.zip"); - - ZipModeImage src_image(true, 10 * 4096); - ZipModeImage tgt_image(false, 10 * 4096); - ASSERT_TRUE(src_image.Initialize(src_path)); - ASSERT_TRUE(tgt_image.Initialize(tgt_path)); - ASSERT_TRUE(ZipModeImage::CheckAndProcessChunks(&tgt_image, &src_image)); - - src_image.DumpChunks(); - tgt_image.DumpChunks(); - - std::vector split_tgt_images; - std::vector split_src_images; - std::vector split_src_ranges; - ZipModeImage::SplitZipModeImageWithLimit(tgt_image, src_image, &split_tgt_images, - &split_src_images, &split_src_ranges); - - // Expected split images with limit = 10 blocks. - // src_piece 0: a 3 blocks, b 5 blocks - // src_piece 1: c 8 blocks - // src_piece 2: d-0 10 block - // src_piece 3: d-1 10 blocks - // src_piece 4: e 1 block, CD - ASSERT_EQ(split_tgt_images.size(), split_src_images.size()); - ASSERT_EQ(static_cast(5), split_tgt_images.size()); - - ASSERT_EQ(static_cast(2), split_src_images[0].NumOfChunks()); - ASSERT_EQ("a", split_src_images[0][0].GetEntryName()); - ASSERT_EQ("b", split_src_images[0][1].GetEntryName()); - - ASSERT_EQ(static_cast(1), split_src_images[1].NumOfChunks()); - ASSERT_EQ("c", split_src_images[1][0].GetEntryName()); - - ASSERT_EQ(static_cast(0), split_src_images[2].NumOfChunks()); - ASSERT_EQ(static_cast(0), split_src_images[3].NumOfChunks()); - ASSERT_EQ(static_cast(0), split_src_images[4].NumOfChunks()); - - // Compute patch. - TemporaryFile patch_file; - TemporaryFile split_info_file; - TemporaryDir debug_dir; - ASSERT_TRUE(ZipModeImage::GeneratePatches(split_tgt_images, split_src_images, split_src_ranges, - patch_file.path, split_info_file.path, debug_dir.path)); - - // Verify the content of split info. - // Expect 5 pieces of patch. ["a","b"; "c"; "d-0"; "d-1"; "e"] - std::string split_info_string; - android::base::ReadFileToString(split_info_file.path, &split_info_string); - std::vector info_list = - android::base::Split(android::base::Trim(split_info_string), "\n"); - - ASSERT_EQ(static_cast(7), info_list.size()); - ASSERT_EQ("2", android::base::Trim(info_list[0])); - ASSERT_EQ("5", android::base::Trim(info_list[1])); - - std::string tgt; - ASSERT_TRUE(android::base::ReadFileToString(tgt_path, &tgt)); - ASSERT_EQ(static_cast(160385), tgt.size()); - std::vector tgt_file_ranges = { - "36864 2,22,31", "32768 2,31,40", "40960 2,0,11", "40960 2,11,21", "8833 4,21,22,40,41", - }; - - for (size_t i = 0; i < 5; i++) { - struct stat st; - std::string path = android::base::StringPrintf("%s/patch-%zu", debug_dir.path, i); - ASSERT_EQ(0, stat(path.c_str(), &st)); - ASSERT_EQ(std::to_string(st.st_size) + " " + tgt_file_ranges[i], - android::base::Trim(info_list[i + 2])); - } - - GenerateAndCheckSplitTarget(debug_dir.path, 5, tgt); -} - -TEST(ImgdiffTest, zip_mode_no_match_source) { - // Generate 20 blocks of random data. - std::string random_data; - random_data.reserve(4096 * 20); - generate_n(back_inserter(random_data), 4096 * 20, []() { return rand() % 256; }); - - TemporaryFile tgt_file; - FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb"); - ZipWriter tgt_writer(tgt_file_ptr); - - construct_deflate_entry({ { "a", 0, 4 }, { "b", 5, 5 }, { "c", 11, 5 } }, &tgt_writer, - random_data); - - ASSERT_EQ(0, tgt_writer.Finish()); - ASSERT_EQ(0, fclose(tgt_file_ptr)); - - // We don't have a matching source entry. - TemporaryFile src_file; - FILE* src_file_ptr = fdopen(src_file.release(), "wb"); - ZipWriter src_writer(src_file_ptr); - construct_store_entry({ { "d", 1, 'd' } }, &src_writer); - ASSERT_EQ(0, src_writer.Finish()); - ASSERT_EQ(0, fclose(src_file_ptr)); - - // Compute patch. - TemporaryFile patch_file; - TemporaryFile split_info_file; - TemporaryDir debug_dir; - std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path); - std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path); - std::vector args = { - "imgdiff", "-z", "--block-limit=10", debug_dir_arg.c_str(), split_info_arg.c_str(), - src_file.path, tgt_file.path, patch_file.path, - }; - ASSERT_EQ(0, imgdiff(args.size(), args.data())); - - std::string tgt; - ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); - - // Expect 1 pieces of patch due to no matching source entry. - GenerateAndCheckSplitTarget(debug_dir.path, 1, tgt); -} - -TEST(ImgdiffTest, zip_mode_large_enough_limit) { - // Generate 20 blocks of random data. - std::string random_data; - random_data.reserve(4096 * 20); - generate_n(back_inserter(random_data), 4096 * 20, []() { return rand() % 256; }); - - TemporaryFile tgt_file; - FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb"); - ZipWriter tgt_writer(tgt_file_ptr); - - construct_deflate_entry({ { "a", 0, 10 }, { "b", 10, 5 } }, &tgt_writer, random_data); - - ASSERT_EQ(0, tgt_writer.Finish()); - ASSERT_EQ(0, fclose(tgt_file_ptr)); - - // Construct 10 blocks of source. - TemporaryFile src_file; - FILE* src_file_ptr = fdopen(src_file.release(), "wb"); - ZipWriter src_writer(src_file_ptr); - construct_deflate_entry({ { "a", 1, 10 } }, &src_writer, random_data); - ASSERT_EQ(0, src_writer.Finish()); - ASSERT_EQ(0, fclose(src_file_ptr)); - - // Compute patch with a limit of 20 blocks. - TemporaryFile patch_file; - TemporaryFile split_info_file; - TemporaryDir debug_dir; - std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path); - std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path); - std::vector args = { - "imgdiff", "-z", "--block-limit=20", split_info_arg.c_str(), debug_dir_arg.c_str(), - src_file.path, tgt_file.path, patch_file.path, - }; - ASSERT_EQ(0, imgdiff(args.size(), args.data())); - - std::string tgt; - ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); - - // Expect 1 piece of patch since limit is larger than the zip file size. - GenerateAndCheckSplitTarget(debug_dir.path, 1, tgt); -} - -TEST(ImgdiffTest, zip_mode_large_apk_small_target_chunk) { - TemporaryFile tgt_file; - FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb"); - ZipWriter tgt_writer(tgt_file_ptr); - - // The first entry is less than 4096 bytes, followed immediately by an entry that has a very - // large counterpart in the source file. Therefore the first entry will be patched separately. - std::string small_chunk("a", 2000); - ASSERT_EQ(0, tgt_writer.StartEntry("a", 0)); - ASSERT_EQ(0, tgt_writer.WriteBytes(small_chunk.data(), small_chunk.size())); - ASSERT_EQ(0, tgt_writer.FinishEntry()); - construct_store_entry( - { - { "b", 12, 'b' }, { "c", 3, 'c' }, - }, - &tgt_writer); - ASSERT_EQ(0, tgt_writer.Finish()); - ASSERT_EQ(0, fclose(tgt_file_ptr)); - - TemporaryFile src_file; - FILE* src_file_ptr = fdopen(src_file.release(), "wb"); - ZipWriter src_writer(src_file_ptr); - construct_store_entry({ { "a", 1, 'a' }, { "b", 13, 'b' }, { "c", 1, 'c' } }, &src_writer); - ASSERT_EQ(0, src_writer.Finish()); - ASSERT_EQ(0, fclose(src_file_ptr)); - - // Compute patch. - TemporaryFile patch_file; - TemporaryFile split_info_file; - TemporaryDir debug_dir; - std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path); - std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path); - std::vector args = { - "imgdiff", "-z", "--block-limit=10", split_info_arg.c_str(), debug_dir_arg.c_str(), - src_file.path, tgt_file.path, patch_file.path, - }; - ASSERT_EQ(0, imgdiff(args.size(), args.data())); - - std::string tgt; - ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); - - // Expect three split src images: - // src_piece 0: a 1 blocks - // src_piece 1: b-0 10 blocks - // src_piece 2: b-1 3 blocks, c 1 blocks, CD - GenerateAndCheckSplitTarget(debug_dir.path, 3, tgt); -} - -TEST(ImgdiffTest, zip_mode_large_apk_skipped_small_target_chunk) { - TemporaryFile tgt_file; - FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb"); - ZipWriter tgt_writer(tgt_file_ptr); - - construct_store_entry( - { - { "a", 11, 'a' }, - }, - &tgt_writer); - - // Construct a tiny target entry of 1 byte, which will be skipped due to the tail alignment of - // the previous entry. - std::string small_chunk("b", 1); - ASSERT_EQ(0, tgt_writer.StartEntry("b", 0)); - ASSERT_EQ(0, tgt_writer.WriteBytes(small_chunk.data(), small_chunk.size())); - ASSERT_EQ(0, tgt_writer.FinishEntry()); - - ASSERT_EQ(0, tgt_writer.Finish()); - ASSERT_EQ(0, fclose(tgt_file_ptr)); - - TemporaryFile src_file; - FILE* src_file_ptr = fdopen(src_file.release(), "wb"); - ZipWriter src_writer(src_file_ptr); - construct_store_entry( - { - { "a", 11, 'a' }, { "b", 11, 'b' }, - }, - &src_writer); - ASSERT_EQ(0, src_writer.Finish()); - ASSERT_EQ(0, fclose(src_file_ptr)); - - // Compute patch. - TemporaryFile patch_file; - TemporaryFile split_info_file; - TemporaryDir debug_dir; - std::string split_info_arg = android::base::StringPrintf("--split-info=%s", split_info_file.path); - std::string debug_dir_arg = android::base::StringPrintf("--debug-dir=%s", debug_dir.path); - std::vector args = { - "imgdiff", "-z", "--block-limit=10", split_info_arg.c_str(), debug_dir_arg.c_str(), - src_file.path, tgt_file.path, patch_file.path, - }; - ASSERT_EQ(0, imgdiff(args.size(), args.data())); - - std::string tgt; - ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt)); - - // Expect two split src images: - // src_piece 0: a-0 10 blocks - // src_piece 1: a-0 1 block, CD - GenerateAndCheckSplitTarget(debug_dir.path, 2, tgt); -} diff --git a/tests/unit/host/update_simulator_test.cpp b/tests/unit/host/update_simulator_test.cpp deleted file mode 100644 index 1603982e..00000000 --- a/tests/unit/host/update_simulator_test.cpp +++ /dev/null @@ -1,403 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "otautil/paths.h" -#include "otautil/print_sha1.h" -#include "updater/blockimg.h" -#include "updater/build_info.h" -#include "updater/install.h" -#include "updater/simulator_runtime.h" -#include "updater/target_files.h" -#include "updater/updater.h" - -using std::string; - -// echo -n "system.img" > system.img && img2simg system.img sparse_system_string_.img 4096 && -// hexdump -v -e '" " 12/1 "0x%02x, " "\n"' sparse_system_string_.img -// The total size of the result sparse image is 4136 bytes; and we can append 0s in the end to get -// the full image. -constexpr uint8_t SPARSE_SYSTEM_HEADER[] = { - 0x3a, 0xff, 0x26, 0xed, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x10, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1, 0xca, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x10, 0x00, 0x00, 0x73, 0x79, 0x73, 0x74, 0x65, - 0x6d, 0x2e, 0x69, 0x6d, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - -static void AddZipEntries(int fd, const std::map& entries) { - FILE* zip_file = fdopen(fd, "w"); - ZipWriter writer(zip_file); - for (const auto& pair : entries) { - ASSERT_EQ(0, writer.StartEntry(pair.first.c_str(), 0)); - ASSERT_EQ(0, writer.WriteBytes(pair.second.data(), pair.second.size())); - ASSERT_EQ(0, writer.FinishEntry()); - } - ASSERT_EQ(0, writer.Finish()); - ASSERT_EQ(0, fclose(zip_file)); -} - -static string CalculateSha1(const string& data) { - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast(data.c_str()), data.size(), digest); - return print_sha1(digest); -} - -static void CreateBsdiffPatch(const string& src, const string& tgt, string* patch) { - TemporaryFile patch_file; - ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast(src.data()), src.size(), - reinterpret_cast(tgt.data()), tgt.size(), - patch_file.path, nullptr)); - ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, patch)); -} - -static void RunSimulation(std::string_view src_tf, std::string_view ota_package, bool expected) { - TemporaryFile cmd_pipe; - TemporaryFile temp_saved_source; - TemporaryFile temp_last_command; - TemporaryDir temp_stash_base; - - Paths::Get().set_cache_temp_source(temp_saved_source.path); - Paths::Get().set_last_command_file(temp_last_command.path); - Paths::Get().set_stash_directory_base(temp_stash_base.path); - - // Configure edify's functions. - RegisterBuiltins(); - RegisterInstallFunctions(); - RegisterBlockImageFunctions(); - - // Run the update simulation and check the result. - TemporaryDir work_dir; - BuildInfo build_info(work_dir.path, false); - ASSERT_TRUE(build_info.ParseTargetFile(src_tf, false)); - Updater updater(std::make_unique(&build_info)); - ASSERT_TRUE(updater.Init(cmd_pipe.release(), ota_package, false)); - ASSERT_EQ(expected, updater.RunUpdate()); - // TODO(xunchang) check the recovery&system has the expected contents. -} - -class DISABLED_UpdateSimulatorTest : public ::testing::Test { - protected: - void SetUp() override { - std::vector props = { - "import /oem/oem.prop oem*", - "# begin build properties", - "# autogenerated by buildinfo.sh", - "ro.build.id=OPR1.170510.001", - "ro.build.display.id=OPR1.170510.001 dev-keys", - "ro.build.version.incremental=3993052", - "ro.build.version.release=O", - "ro.build.date=Wed May 10 11:10:29 UTC 2017", - "ro.build.date.utc=1494414629", - "ro.build.type=user", - "ro.build.tags=dev-keys", - "ro.build.flavor=angler-user", - "ro.product.system.brand=google", - "ro.product.system.name=angler", - "ro.product.system.device=angler", - }; - build_prop_string_ = android::base::Join(props, "\n"); - - fstab_content_ = R"( -# -# More comments..... - -/dev/block/by-name/system /system ext4 ro,barrier=1 wait -/dev/block/by-name/vendor /vendor ext4 ro wait,verify=/dev/metadata -/dev/block/by-name/cache /cache ext4 noatime,errors=panic wait,check -/dev/block/by-name/modem /firmware vfat ro,uid=1000,gid=1000, wait -/dev/block/by-name/boot /boot emmc defaults defaults -/dev/block/by-name/recovery /recovery emmc defaults defaults -/dev/block/by-name/misc /misc emmc defaults -/dev/block/by-name/modem /modem emmc defaults defaults)"; - - raw_system_string_ = "system.img" + string(4086, '\0'); // raw image is 4096 bytes in total - sparse_system_string_ = string(SPARSE_SYSTEM_HEADER, std::end(SPARSE_SYSTEM_HEADER)) + - string(4136 - sizeof(SPARSE_SYSTEM_HEADER), '\0'); - } - - string build_prop_string_; - string fstab_content_; - string raw_system_string_; - string sparse_system_string_; -}; - -TEST_F(DISABLED_UpdateSimulatorTest, TargetFile_ExtractImage) { - TemporaryFile zip_file; - AddZipEntries(zip_file.release(), { { "META/misc_info.txt", "extfs_sparse_flag=-s" }, - { "IMAGES/system.img", sparse_system_string_ } }); - TargetFile target_file(zip_file.path, false); - ASSERT_TRUE(target_file.Open()); - - TemporaryDir temp_dir; - TemporaryFile raw_image; - ASSERT_TRUE(target_file.ExtractImage( - "IMAGES/system.img", FstabInfo("/dev/system", "system", "ext4"), temp_dir.path, &raw_image)); - - // Check the raw image has expected contents. - string content; - ASSERT_TRUE(android::base::ReadFileToString(raw_image.path, &content)); - string expected_content = "system.img" + string(4086, '\0'); - ASSERT_EQ(expected_content, content); -} - -TEST_F(DISABLED_UpdateSimulatorTest, TargetFile_ParseFstabInfo) { - TemporaryFile zip_file; - AddZipEntries(zip_file.release(), - { { "META/misc_info.txt", "" }, - { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ } }); - TargetFile target_file(zip_file.path, false); - ASSERT_TRUE(target_file.Open()); - - std::vector fstab_info; - EXPECT_TRUE(target_file.ParseFstabInfo(&fstab_info)); - - std::vector> transformed; - std::transform(fstab_info.begin(), fstab_info.end(), std::back_inserter(transformed), - [](const FstabInfo& info) { - return std::vector{ info.blockdev_name, info.mount_point, info.fs_type }; - }); - - std::vector> expected = { - { "/dev/block/by-name/system", "/system", "ext4" }, - { "/dev/block/by-name/vendor", "/vendor", "ext4" }, - { "/dev/block/by-name/cache", "/cache", "ext4" }, - { "/dev/block/by-name/boot", "/boot", "emmc" }, - { "/dev/block/by-name/recovery", "/recovery", "emmc" }, - { "/dev/block/by-name/misc", "/misc", "emmc" }, - { "/dev/block/by-name/modem", "/modem", "emmc" }, - }; - EXPECT_EQ(expected, transformed); -} - -TEST_F(DISABLED_UpdateSimulatorTest, BuildInfo_ParseTargetFile) { - std::map entries = { - { "META/misc_info.txt", "" }, - { "SYSTEM/build.prop", build_prop_string_ }, - { "RECOVERY/RAMDISK/system/etc/recovery.fstab", fstab_content_ }, - { "IMAGES/recovery.img", "" }, - { "IMAGES/boot.img", "" }, - { "IMAGES/misc.img", "" }, - { "IMAGES/system.map", "" }, - { "IMAGES/system.img", sparse_system_string_ }, - }; - - TemporaryFile zip_file; - AddZipEntries(zip_file.release(), entries); - - TemporaryDir temp_dir; - BuildInfo build_info(temp_dir.path, false); - ASSERT_TRUE(build_info.ParseTargetFile(zip_file.path, false)); - - std::map expected_result = { - { "ro.build.id", "OPR1.170510.001" }, - { "ro.build.display.id", "OPR1.170510.001 dev-keys" }, - { "ro.build.version.incremental", "3993052" }, - { "ro.build.version.release", "O" }, - { "ro.build.date", "Wed May 10 11:10:29 UTC 2017" }, - { "ro.build.date.utc", "1494414629" }, - { "ro.build.type", "user" }, - { "ro.build.tags", "dev-keys" }, - { "ro.build.flavor", "angler-user" }, - { "ro.product.brand", "google" }, - { "ro.product.name", "angler" }, - { "ro.product.device", "angler" }, - }; - - for (const auto& [key, value] : expected_result) { - ASSERT_EQ(value, build_info.GetProperty(key, "")); - } - - // Check that the temp files for each block device are created successfully. - for (auto name : { "/dev/block/by-name/system", "/dev/block/by-name/recovery", - "/dev/block/by-name/boot", "/dev/block/by-name/misc" }) { - ASSERT_EQ(0, access(build_info.FindBlockDeviceName(name).c_str(), R_OK)); - } -} - -TEST_F(DISABLED_UpdateSimulatorTest, RunUpdateSmoke) { - string recovery_img_string = "recovery.img"; - string boot_img_string = "boot.img"; - - std::map src_entries{ - { "META/misc_info.txt", "extfs_sparse_flag=-s" }, - { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, - { "SYSTEM/build.prop", build_prop_string_ }, - { "IMAGES/recovery.img", "" }, - { "IMAGES/boot.img", boot_img_string }, - { "IMAGES/system.img", sparse_system_string_ }, - }; - - // Construct the source target-files. - TemporaryFile src_tf; - AddZipEntries(src_tf.release(), src_entries); - - string recovery_from_boot; - CreateBsdiffPatch(boot_img_string, recovery_img_string, &recovery_from_boot); - - // Set up the apply patch commands to patch the recovery image. - string recovery_sha1 = CalculateSha1(recovery_img_string); - string boot_sha1 = CalculateSha1(boot_img_string); - string apply_patch_source_string = android::base::StringPrintf( - "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str()); - string apply_patch_target_string = android::base::StringPrintf( - "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str()); - string check_command = android::base::StringPrintf( - R"(patch_partition_check("%s", "%s") || abort("check failed");)", - apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); - string patch_command = android::base::StringPrintf( - R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("patch failed");)", - apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); - - // Add the commands to update the system image. Test common commands: - // * getprop - // * ui_print - // * patch_partition - // * package_extract_file (single argument) - // * block_image_verify, block_image_update - string tgt_system_string = string(4096, 'a'); - string system_patch; - CreateBsdiffPatch(raw_system_string_, tgt_system_string, &system_patch); - - string tgt_system_hash = CalculateSha1(tgt_system_string); - string src_system_hash = CalculateSha1(raw_system_string_); - - std::vector transfer_list = { - "4", - "1", - "0", - "0", - android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,1 1 2,0,1", system_patch.size(), - src_system_hash.c_str(), tgt_system_hash.c_str()), - }; - - // Construct the updater_script. - std::vector updater_commands = { - R"(getprop("ro.product.device") == "angler" || abort("This package is for \"angler\"");)", - R"(ui_print("Source: angler/OPR1.170510.001");)", - check_command, - patch_command, - R"(block_image_verify("/dev/block/by-name/system", )" - R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )" - R"(abort("Failed to verify system.");)", - R"(block_image_update("/dev/block/by-name/system", )" - R"(package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat") || )" - R"(abort("Failed to verify system.");)", - }; - string updater_script = android::base::Join(updater_commands, '\n'); - - // Construct the ota update package. - std::map ota_entries{ - { "system.new.dat", "" }, - { "system.patch.dat", system_patch }, - { "system.transfer.list", android::base::Join(transfer_list, '\n') }, - { "META-INF/com/google/android/updater-script", updater_script }, - { "patch.p", recovery_from_boot }, - }; - - TemporaryFile ota_package; - AddZipEntries(ota_package.release(), ota_entries); - - RunSimulation(src_tf.path, ota_package.path, true); -} - -TEST_F(DISABLED_UpdateSimulatorTest, RunUpdateUnrecognizedFunction) { - std::map src_entries{ - { "META/misc_info.txt", "extfs_sparse_flag=-s" }, - { "IMAGES/system.img", sparse_system_string_ }, - { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, - { "SYSTEM/build.prop", build_prop_string_ }, - }; - - TemporaryFile src_tf; - AddZipEntries(src_tf.release(), src_entries); - - std::map ota_entries{ - { "system.new.dat", "" }, - { "system.patch.dat", "" }, - { "system.transfer.list", "" }, - { "META-INF/com/google/android/updater-script", R"(bad_function("");)" }, - }; - - TemporaryFile ota_package; - AddZipEntries(ota_package.release(), ota_entries); - - RunSimulation(src_tf.path, ota_package.path, false); -} - -TEST_F(DISABLED_UpdateSimulatorTest, RunUpdateApplyPatchFailed) { - string recovery_img_string = "recovery.img"; - string boot_img_string = "boot.img"; - - std::map src_entries{ - { "META/misc_info.txt", "extfs_sparse_flag=-s" }, - { "IMAGES/recovery.img", "" }, - { "IMAGES/boot.img", boot_img_string }, - { "IMAGES/system.img", sparse_system_string_ }, - { "RECOVERY/RAMDISK/etc/recovery.fstab", fstab_content_ }, - { "SYSTEM/build.prop", build_prop_string_ }, - }; - - TemporaryFile src_tf; - AddZipEntries(src_tf.release(), src_entries); - - string recovery_sha1 = CalculateSha1(recovery_img_string); - string boot_sha1 = CalculateSha1(boot_img_string); - string apply_patch_source_string = android::base::StringPrintf( - "EMMC:/dev/block/by-name/boot:%zu:%s", boot_img_string.size(), boot_sha1.c_str()); - string apply_patch_target_string = android::base::StringPrintf( - "EMMC:/dev/block/by-name/recovery:%zu:%s", recovery_img_string.size(), recovery_sha1.c_str()); - string check_command = android::base::StringPrintf( - R"(patch_partition_check("%s", "%s") || abort("check failed");)", - apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); - string patch_command = android::base::StringPrintf( - R"(patch_partition("%s", "%s", package_extract_file("patch.p")) || abort("failed");)", - apply_patch_target_string.c_str(), apply_patch_source_string.c_str()); - - // Give an invalid recovery patch and expect the apply patch to fail. - // TODO(xunchang) check the cause code. - std::vector updater_commands = { - R"(ui_print("Source: angler/OPR1.170510.001");)", - check_command, - patch_command, - }; - - string updater_script = android::base::Join(updater_commands, '\n'); - std::map ota_entries{ - { "system.new.dat", "" }, - { "system.patch.dat", "" }, - { "system.transfer.list", "" }, - { "META-INF/com/google/android/updater-script", updater_script }, - { "patch.p", "random string" }, - }; - - TemporaryFile ota_package; - AddZipEntries(ota_package.release(), ota_entries); - - RunSimulation(src_tf.path, ota_package.path, false); -} diff --git a/tests/unit/updater_test.cpp b/tests/unit/updater_test.cpp deleted file mode 100644 index 8993dd8b..00000000 --- a/tests/unit/updater_test.cpp +++ /dev/null @@ -1,1227 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "applypatch/applypatch.h" -#include "common/test_constants.h" -#include "edify/expr.h" -#include "otautil/error_code.h" -#include "otautil/paths.h" -#include "otautil/print_sha1.h" -#include "otautil/sysutil.h" -#include "private/commands.h" -#include "updater/blockimg.h" -#include "updater/install.h" -#include "updater/updater.h" -#include "updater/updater_runtime.h" - -using namespace std::string_literals; - -using PackageEntries = std::unordered_map; - -static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code, - Updater* updater) { - std::unique_ptr e; - int error_count = 0; - ASSERT_EQ(0, ParseString(expr_str, &e, &error_count)); - ASSERT_EQ(0, error_count); - - State state(expr_str, updater); - - std::string result; - bool status = Evaluate(&state, e, &result); - - if (expected == nullptr) { - ASSERT_FALSE(status); - } else { - ASSERT_TRUE(status) << "Evaluate() finished with error message: " << state.errmsg; - ASSERT_STREQ(expected, result.c_str()); - } - - // Error code is set in updater/updater.cpp only, by parsing State.errmsg. - ASSERT_EQ(kNoError, state.error_code); - - // Cause code should always be available. - ASSERT_EQ(cause_code, state.cause_code); -} - -static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code) { - Updater updater(std::make_unique(nullptr)); - expect(expected, expr_str, cause_code, &updater); -} - -static void BuildUpdatePackage(const PackageEntries& entries, int fd) { - FILE* zip_file_ptr = fdopen(fd, "wb"); - ZipWriter zip_writer(zip_file_ptr); - - for (const auto& entry : entries) { - // All the entries are written as STORED. - ASSERT_EQ(0, zip_writer.StartEntry(entry.first.c_str(), 0)); - if (!entry.second.empty()) { - ASSERT_EQ(0, zip_writer.WriteBytes(entry.second.data(), entry.second.size())); - } - ASSERT_EQ(0, zip_writer.FinishEntry()); - } - - ASSERT_EQ(0, zip_writer.Finish()); - ASSERT_EQ(0, fclose(zip_file_ptr)); -} - -static std::string GetSha1(std::string_view content) { - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast(content.data()), content.size(), digest); - return print_sha1(digest); -} - -static Value* BlobToString(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); - } - - std::vector> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } - - if (args[0]->type != Value::Type::BLOB) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects a BLOB argument", name); - } - - args[0]->type = Value::Type::STRING; - return args[0].release(); -} - -class UpdaterTestBase { - protected: - UpdaterTestBase() : updater_(std::make_unique(nullptr)) {} - - void SetUp() { - RegisterBuiltins(); - RegisterInstallFunctions(); - RegisterBlockImageFunctions(); - - // Each test is run in a separate process (isolated mode). Shared temporary files won't cause - // conflicts. - Paths::Get().set_cache_temp_source(temp_saved_source_.path); - Paths::Get().set_last_command_file(temp_last_command_.path); - Paths::Get().set_stash_directory_base(temp_stash_base_.path); - - last_command_file_ = temp_last_command_.path; - image_file_ = image_temp_file_.path; - } - - void TearDown() { - // Clean up the last_command_file if any. - ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_)); - - // Clear partition updated marker if any. - std::string updated_marker{ temp_stash_base_.path }; - updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED"; - ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker)); - } - - void RunBlockImageUpdate(bool is_verify, PackageEntries entries, const std::string& image_file, - const std::string& result, CauseCode cause_code = kNoCause) { - CHECK(entries.find("transfer_list") != entries.end()); - std::string new_data = - entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data"; - std::string script = is_verify ? "block_image_verify" : "block_image_update"; - script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data + - R"(", "patch_data"))"; - entries.emplace(Updater::SCRIPT_NAME, script); - - // Build the update package. - TemporaryFile zip_file; - BuildUpdatePackage(entries, zip_file.release()); - - // Set up the handler, command_pipe, patch offset & length. - TemporaryFile temp_pipe; - ASSERT_TRUE(updater_.Init(temp_pipe.release(), zip_file.path, false)); - ASSERT_TRUE(updater_.RunUpdate()); - ASSERT_EQ(result, updater_.GetResult()); - - // Parse the cause code written to the command pipe. - int received_cause_code = kNoCause; - std::string pipe_content; - ASSERT_TRUE(android::base::ReadFileToString(temp_pipe.path, &pipe_content)); - auto lines = android::base::Split(pipe_content, "\n"); - for (std::string_view line : lines) { - if (android::base::ConsumePrefix(&line, "log cause: ")) { - ASSERT_TRUE(android::base::ParseInt(line.data(), &received_cause_code)); - } - } - ASSERT_EQ(cause_code, received_cause_code); - } - - TemporaryFile temp_saved_source_; - TemporaryDir temp_stash_base_; - std::string last_command_file_; - std::string image_file_; - - Updater updater_; - - private: - TemporaryFile temp_last_command_; - TemporaryFile image_temp_file_; -}; - -class UpdaterTest : public UpdaterTestBase, public ::testing::Test { - protected: - void SetUp() override { - UpdaterTestBase::SetUp(); - - RegisterFunction("blob_to_string", BlobToString); - // Enable a special command "abort" to simulate interruption. - Command::abort_allowed_ = true; - } - - void TearDown() override { - UpdaterTestBase::TearDown(); - } - - void SetUpdaterCmdPipe(int fd) { - FILE* cmd_pipe = fdopen(fd, "w"); - ASSERT_NE(nullptr, cmd_pipe); - updater_.cmd_pipe_.reset(cmd_pipe); - } - - void SetUpdaterOtaPackageHandle(ZipArchiveHandle handle) { - updater_.package_handle_ = handle; - } - - void FlushUpdaterCommandPipe() const { - fflush(updater_.cmd_pipe_.get()); - } -}; - -TEST_F(UpdaterTest, getprop) { - expect(android::base::GetProperty("ro.product.device", "").c_str(), - "getprop(\"ro.product.device\")", - kNoCause); - - expect(android::base::GetProperty("ro.build.fingerprint", "").c_str(), - "getprop(\"ro.build.fingerprint\")", - kNoCause); - - // getprop() accepts only one parameter. - expect(nullptr, "getprop()", kArgsParsingFailure); - expect(nullptr, "getprop(\"arg1\", \"arg2\")", kArgsParsingFailure); -} - -TEST_F(UpdaterTest, patch_partition_check) { - // Zero argument is not valid. - expect(nullptr, "patch_partition_check()", kArgsParsingFailure); - - std::string source_file = from_testdata_base("boot.img"); - std::string source_content; - ASSERT_TRUE(android::base::ReadFileToString(source_file, &source_content)); - size_t source_size = source_content.size(); - std::string source_hash = GetSha1(source_content); - Partition source(source_file, source_size, source_hash); - - std::string target_file = from_testdata_base("recovery.img"); - std::string target_content; - ASSERT_TRUE(android::base::ReadFileToString(target_file, &target_content)); - size_t target_size = target_content.size(); - std::string target_hash = GetSha1(target_content); - Partition target(target_file, target_size, target_hash); - - // One argument is not valid. - expect(nullptr, "patch_partition_check(\"" + source.ToString() + "\")", kArgsParsingFailure); - expect(nullptr, "patch_partition_check(\"" + target.ToString() + "\")", kArgsParsingFailure); - - // Both of the source and target have the desired checksum. - std::string cmd = - "patch_partition_check(\"" + source.ToString() + "\", \"" + target.ToString() + "\")"; - expect("t", cmd, kNoCause); - - // Only source partition has the desired checksum. - Partition bad_target(target_file, target_size - 1, target_hash); - cmd = "patch_partition_check(\"" + source.ToString() + "\", \"" + bad_target.ToString() + "\")"; - expect("t", cmd, kNoCause); - - // Only target partition has the desired checksum. - Partition bad_source(source_file, source_size + 1, source_hash); - cmd = "patch_partition_check(\"" + bad_source.ToString() + "\", \"" + target.ToString() + "\")"; - expect("t", cmd, kNoCause); - - // Neither of the source or target has the desired checksum. - cmd = - "patch_partition_check(\"" + bad_source.ToString() + "\", \"" + bad_target.ToString() + "\")"; - expect("", cmd, kNoCause); -} - -TEST_F(UpdaterTest, file_getprop) { - // file_getprop() expects two arguments. - expect(nullptr, "file_getprop()", kArgsParsingFailure); - expect(nullptr, "file_getprop(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "file_getprop(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // File doesn't exist. - expect(nullptr, "file_getprop(\"/doesntexist\", \"key1\")", kFreadFailure); - - // Reject too large files (current limit = 65536). - TemporaryFile temp_file1; - std::string buffer(65540, '\0'); - ASSERT_TRUE(android::base::WriteStringToFile(buffer, temp_file1.path)); - - // Read some keys. - TemporaryFile temp_file2; - std::string content("ro.product.name=tardis\n" - "# comment\n\n\n" - "ro.product.model\n" - "ro.product.board = magic \n"); - ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file2.path)); - - std::string script1("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.name\")"); - expect("tardis", script1, kNoCause); - - std::string script2("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.board\")"); - expect("magic", script2, kNoCause); - - // No match. - std::string script3("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.wrong\")"); - expect("", script3, kNoCause); - - std::string script4("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.name=\")"); - expect("", script4, kNoCause); - - std::string script5("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.nam\")"); - expect("", script5, kNoCause); - - std::string script6("file_getprop(\"" + std::string(temp_file2.path) + - "\", \"ro.product.model\")"); - expect("", script6, kNoCause); -} - -// TODO: Test extracting to block device. -TEST_F(UpdaterTest, package_extract_file) { - // package_extract_file expects 1 or 2 arguments. - expect(nullptr, "package_extract_file()", kArgsParsingFailure); - expect(nullptr, "package_extract_file(\"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. - SetUpdaterOtaPackageHandle(handle); - - // Two-argument version. - TemporaryFile temp_file1; - std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")"); - expect("t", script, kNoCause, &updater_); - - // Verify the extracted entry. - std::string data; - ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data)); - ASSERT_EQ(kATxtContents, data); - - // Now extract another entry to the same location, which should overwrite. - script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")"; - expect("t", script, kNoCause, &updater_); - - ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data)); - ASSERT_EQ(kBTxtContents, data); - - // Missing zip entry. The two-argument version doesn't abort. - script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")"; - expect("", script, kNoCause, &updater_); - - // Extract to /dev/full should fail. - script = "package_extract_file(\"a.txt\", \"/dev/full\")"; - expect("", script, kNoCause, &updater_); - - // One-argument version. package_extract_file() gives a VAL_BLOB, which needs to be converted to - // VAL_STRING for equality test. - script = "blob_to_string(package_extract_file(\"a.txt\")) == \"" + kATxtContents + "\""; - expect("t", script, kNoCause, &updater_); - - script = "blob_to_string(package_extract_file(\"b.txt\")) == \"" + kBTxtContents + "\""; - expect("t", script, kNoCause, &updater_); - - // Missing entry. The one-argument version aborts the evaluation. - script = "package_extract_file(\"doesntexist\")"; - expect(nullptr, script, kPackageExtractFileFailure, &updater_); -} - -TEST_F(UpdaterTest, read_file) { - // read_file() expects one argument. - expect(nullptr, "read_file()", kArgsParsingFailure); - expect(nullptr, "read_file(\"arg1\", \"arg2\")", kArgsParsingFailure); - - // Write some value to file and read back. - TemporaryFile temp_file; - std::string script("write_value(\"foo\", \""s + temp_file.path + "\");"); - expect("t", script, kNoCause); - - script = "read_file(\""s + temp_file.path + "\") == \"foo\""; - expect("t", script, kNoCause); - - script = "read_file(\""s + temp_file.path + "\") == \"bar\""; - expect("", script, kNoCause); - - // It should fail gracefully when read fails. - script = "read_file(\"/doesntexist\")"; - expect("", script, kNoCause); -} - -TEST_F(UpdaterTest, compute_hash_tree_smoke) { - std::string data; - for (unsigned char i = 0; i < 128; i++) { - data += std::string(4096, i); - } - // Appends an additional block for verity data. - data += std::string(4096, 0); - ASSERT_EQ(129 * 4096, data.size()); - ASSERT_TRUE(android::base::WriteStringToFile(data, image_file_)); - - std::string salt = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"; - std::string expected_root_hash = - "7e0a8d8747f54384014ab996f5b2dc4eb7ff00c630eede7134c9e3f05c0dd8ca"; - // hash_tree_ranges, source_ranges, hash_algorithm, salt_hex, root_hash - std::vector tokens{ "compute_hash_tree", "2,128,129", "2,0,128", "sha256", salt, - expected_root_hash }; - std::string hash_tree_command = android::base::Join(tokens, " "); - - std::vector transfer_list{ - "4", "2", "0", "2", hash_tree_command, - }; - - PackageEntries entries{ - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, "\n") }, - }; - - RunBlockImageUpdate(false, entries, image_file_, "t"); - - std::string updated; - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated)); - ASSERT_EQ(129 * 4096, updated.size()); - ASSERT_EQ(data.substr(0, 128 * 4096), updated.substr(0, 128 * 4096)); - - // Computes the SHA256 of the salt + hash_tree_data and expects the result to match with the - // root_hash. - std::vector salt_bytes; - ASSERT_TRUE(HashTreeBuilder::ParseBytesArrayFromString(salt, &salt_bytes)); - std::vector hash_tree = std::move(salt_bytes); - hash_tree.insert(hash_tree.end(), updated.begin() + 128 * 4096, updated.end()); - - std::vector digest(SHA256_DIGEST_LENGTH); - SHA256(hash_tree.data(), hash_tree.size(), digest.data()); - ASSERT_EQ(expected_root_hash, HashTreeBuilder::BytesArrayToString(digest)); -} - -TEST_F(UpdaterTest, compute_hash_tree_root_mismatch) { - std::string data; - for (size_t i = 0; i < 128; i++) { - data += std::string(4096, i); - } - // Appends an additional block for verity data. - data += std::string(4096, 0); - ASSERT_EQ(129 * 4096, data.size()); - // Corrupts one bit - data[4096] = 'A'; - ASSERT_TRUE(android::base::WriteStringToFile(data, image_file_)); - - std::string salt = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7"; - std::string expected_root_hash = - "7e0a8d8747f54384014ab996f5b2dc4eb7ff00c630eede7134c9e3f05c0dd8ca"; - // hash_tree_ranges, source_ranges, hash_algorithm, salt_hex, root_hash - std::vector tokens{ "compute_hash_tree", "2,128,129", "2,0,128", "sha256", salt, - expected_root_hash }; - std::string hash_tree_command = android::base::Join(tokens, " "); - - std::vector transfer_list{ - "4", "2", "0", "2", hash_tree_command, - }; - - PackageEntries entries{ - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, "\n") }, - }; - - RunBlockImageUpdate(false, entries, image_file_, "", kHashTreeComputationFailure); -} - -TEST_F(UpdaterTest, write_value) { - // write_value() expects two arguments. - expect(nullptr, "write_value()", kArgsParsingFailure); - expect(nullptr, "write_value(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "write_value(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // filename cannot be empty. - expect(nullptr, "write_value(\"value\", \"\")", kArgsParsingFailure); - - // Write some value to file. - TemporaryFile temp_file; - std::string value = "magicvalue"; - std::string script("write_value(\"" + value + "\", \"" + std::string(temp_file.path) + "\")"); - expect("t", script, kNoCause); - - // Verify the content. - std::string content; - ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content)); - ASSERT_EQ(value, content); - - // Allow writing empty string. - script = "write_value(\"\", \"" + std::string(temp_file.path) + "\")"; - expect("t", script, kNoCause); - - // Verify the content. - ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content)); - ASSERT_EQ("", content); - - // It should fail gracefully when write fails. - script = "write_value(\"value\", \"/proc/0/file1\")"; - expect("", script, kNoCause); -} - -TEST_F(UpdaterTest, get_stage) { - // get_stage() expects one argument. - expect(nullptr, "get_stage()", kArgsParsingFailure); - expect(nullptr, "get_stage(\"arg1\", \"arg2\")", kArgsParsingFailure); - expect(nullptr, "get_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // Set up a local file as BCB. - TemporaryFile tf; - std::string temp_file(tf.path); - bootloader_message boot; - strlcpy(boot.stage, "2/3", sizeof(boot.stage)); - std::string err; - ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err)); - - // Can read the stage value. - std::string script("get_stage(\"" + temp_file + "\")"); - expect("2/3", script, kNoCause); - - // Bad BCB path. - script = "get_stage(\"doesntexist\")"; - expect("", script, kNoCause); -} - -TEST_F(UpdaterTest, set_stage) { - // set_stage() expects two arguments. - expect(nullptr, "set_stage()", kArgsParsingFailure); - expect(nullptr, "set_stage(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "set_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // Set up a local file as BCB. - TemporaryFile tf; - std::string temp_file(tf.path); - bootloader_message boot; - strlcpy(boot.command, "command", sizeof(boot.command)); - strlcpy(boot.stage, "2/3", sizeof(boot.stage)); - std::string err; - ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err)); - - // Write with set_stage(). - std::string script("set_stage(\"" + temp_file + "\", \"1/3\")"); - expect(tf.path, script, kNoCause); - - // Verify. - bootloader_message boot_verify; - ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_file, &err)); - - // Stage should be updated, with command part untouched. - ASSERT_STREQ("1/3", boot_verify.stage); - ASSERT_STREQ(boot.command, boot_verify.command); - - // Bad BCB path. - script = "set_stage(\"doesntexist\", \"1/3\")"; - expect("", script, kNoCause); - - script = "set_stage(\"/dev/full\", \"1/3\")"; - expect("", script, kNoCause); -} - -TEST_F(UpdaterTest, set_progress) { - // set_progress() expects one argument. - expect(nullptr, "set_progress()", kArgsParsingFailure); - expect(nullptr, "set_progress(\"arg1\", \"arg2\")", kArgsParsingFailure); - - // Invalid progress argument. - expect(nullptr, "set_progress(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "set_progress(\"3x+5\")", kArgsParsingFailure); - expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure); - - TemporaryFile tf; - SetUpdaterCmdPipe(tf.release()); - expect(".52", "set_progress(\".52\")", kNoCause, &updater_); - FlushUpdaterCommandPipe(); - - std::string cmd; - ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd)); - ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd); - // recovery-updater protocol expects 2 tokens ("set_progress "). - ASSERT_EQ(2U, android::base::Split(cmd, " ").size()); -} - -TEST_F(UpdaterTest, show_progress) { - // show_progress() expects two arguments. - expect(nullptr, "show_progress()", kArgsParsingFailure); - expect(nullptr, "show_progress(\"arg1\")", kArgsParsingFailure); - expect(nullptr, "show_progress(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); - - // Invalid progress arguments. - expect(nullptr, "show_progress(\"arg1\", \"arg2\")", kArgsParsingFailure); - expect(nullptr, "show_progress(\"3x+5\", \"10\")", kArgsParsingFailure); - expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure); - - TemporaryFile tf; - SetUpdaterCmdPipe(tf.release()); - expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_); - FlushUpdaterCommandPipe(); - - std::string cmd; - ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd)); - ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd); - // recovery-updater protocol expects 3 tokens ("progress "). - ASSERT_EQ(3U, android::base::Split(cmd, " ").size()); -} - -TEST_F(UpdaterTest, block_image_update_parsing_error) { - std::vector transfer_list{ - // clang-format off - "4", - "2", - "0", - // clang-format on - }; - - PackageEntries entries{ - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - RunBlockImageUpdate(false, entries, image_file_, "", kArgsParsingFailure); -} - -// Generates the bsdiff of the given source and target images, and writes the result entries. -// target_blocks specifies the block count to be written into the `bsdiff` command, which may be -// different from the given target size in order to trigger overrun / underrun paths. -static void GetEntriesForBsdiff(std::string_view source, std::string_view target, - size_t target_blocks, PackageEntries* entries) { - // Generate the patch data. - TemporaryFile patch_file; - ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast(source.data()), source.size(), - reinterpret_cast(target.data()), target.size(), - patch_file.path, nullptr)); - std::string patch_content; - ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content)); - - // Create the transfer list that contains a bsdiff. - std::string src_hash = GetSha1(source); - std::string tgt_hash = GetSha1(target); - size_t source_blocks = source.size() / 4096; - std::vector transfer_list{ - // clang-format off - "4", - std::to_string(target_blocks), - "0", - "0", - // bsdiff patch_offset patch_length source_hash target_hash target_range source_block_count - // source_range - android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,%zu %zu 2,0,%zu", patch_content.size(), - src_hash.c_str(), tgt_hash.c_str(), target_blocks, source_blocks, - source_blocks), - // clang-format on - }; - - *entries = { - { "new_data", "" }, - { "patch_data", patch_content }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; -} - -TEST_F(UpdaterTest, block_image_update_patch_data) { - // Both source and target images have 10 blocks. - std::string source = - std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0'); - std::string target = - std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0'); - ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_)); - - PackageEntries entries; - GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2), - std::string_view(target).substr(0, 4096 * 2), 2, &entries); - RunBlockImageUpdate(false, entries, image_file_, "t"); - - // The update_file should be patched correctly. - std::string updated; - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated)); - ASSERT_EQ(target, updated); -} - -TEST_F(UpdaterTest, block_image_update_patch_overrun) { - // Both source and target images have 10 blocks. - std::string source = - std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0'); - std::string target = - std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0'); - ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_)); - - // Provide one less block to trigger the overrun path. - PackageEntries entries; - GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2), - std::string_view(target).substr(0, 4096 * 2), 1, &entries); - - // The update should fail due to overrun. - RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure); -} - -TEST_F(UpdaterTest, block_image_update_patch_underrun) { - // Both source and target images have 10 blocks. - std::string source = - std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0'); - std::string target = - std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0'); - ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_)); - - // Provide one more block to trigger the overrun path. - PackageEntries entries; - GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2), - std::string_view(target).substr(0, 4096 * 2), 3, &entries); - - // The update should fail due to underrun. - RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure); -} - -TEST_F(UpdaterTest, block_image_update_fail) { - std::string src_content(4096 * 2, 'e'); - std::string src_hash = GetSha1(src_content); - // Stash and free some blocks, then fail the update intentionally. - std::vector transfer_list{ - // clang-format off - "4", - "2", - "0", - "2", - "stash " + src_hash + " 2,0,2", - "free " + src_hash, - "abort", - // clang-format on - }; - - // Add a new data of 10 bytes to test the deadlock. - PackageEntries entries{ - { "new_data", std::string(10, 0) }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - ASSERT_TRUE(android::base::WriteStringToFile(src_content, image_file_)); - - RunBlockImageUpdate(false, entries, image_file_, ""); - - // Updater generates the stash name based on the input file name. - std::string name_digest = GetSha1(image_file_); - std::string stash_base = std::string(temp_stash_base_.path) + "/" + name_digest; - ASSERT_EQ(0, access(stash_base.c_str(), F_OK)); - // Expect the stashed blocks to be freed. - ASSERT_EQ(-1, access((stash_base + src_hash).c_str(), F_OK)); - ASSERT_EQ(0, rmdir(stash_base.c_str())); -} - -TEST_F(UpdaterTest, new_data_over_write) { - std::vector transfer_list{ - // clang-format off - "4", - "1", - "0", - "0", - "new 2,0,1", - // clang-format on - }; - - // Write 4096 + 100 bytes of new data. - PackageEntries entries{ - { "new_data", std::string(4196, 0) }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - RunBlockImageUpdate(false, entries, image_file_, "t"); -} - -TEST_F(UpdaterTest, new_data_short_write) { - std::vector transfer_list{ - // clang-format off - "4", - "1", - "0", - "0", - "new 2,0,1", - // clang-format on - }; - - PackageEntries entries{ - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - // Updater should report the failure gracefully rather than stuck in deadlock. - entries["new_data"] = ""; - RunBlockImageUpdate(false, entries, image_file_, ""); - - entries["new_data"] = std::string(10, 'a'); - RunBlockImageUpdate(false, entries, image_file_, ""); - - // Expect to write 1 block of new data successfully. - entries["new_data"] = std::string(4096, 'a'); - RunBlockImageUpdate(false, entries, image_file_, "t"); -} - -TEST_F(UpdaterTest, brotli_new_data) { - 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::string encoded_data(encoded_size, 0); - ASSERT_TRUE(BrotliEncoderCompress( - BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(), - reinterpret_cast(brotli_new_data.data()), &encoded_size, - reinterpret_cast(const_cast(encoded_data.data())))); - encoded_data.resize(encoded_size); - - // 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 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", - }; - - PackageEntries entries{ - { "new_data.br", std::move(encoded_data) }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list, '\n') }, - }; - - RunBlockImageUpdate(false, entries, image_file_, "t"); - - std::string updated_content; - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_content)); - ASSERT_EQ(brotli_new_data, updated_content); -} - -TEST_F(UpdaterTest, last_command_update) { - std::string block1(4096, '1'); - std::string block2(4096, '2'); - std::string block3(4096, '3'); - std::string block1_hash = GetSha1(block1); - std::string block2_hash = GetSha1(block2); - std::string block3_hash = GetSha1(block3); - - // Compose the transfer list to fail the first update. - std::vector transfer_list_fail{ - // clang-format off - "4", - "2", - "0", - "2", - "stash " + block1_hash + " 2,0,1", - "move " + block1_hash + " 2,1,2 1 2,0,1", - "stash " + block3_hash + " 2,2,3", - "abort", - // clang-format on - }; - - // Mimic a resumed update with the same transfer commands. - std::vector transfer_list_continue{ - // clang-format off - "4", - "2", - "0", - "2", - "stash " + block1_hash + " 2,0,1", - "move " + block1_hash + " 2,1,2 1 2,0,1", - "stash " + block3_hash + " 2,2,3", - "move " + block1_hash + " 2,2,3 1 2,0,1", - // clang-format on - }; - - ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_)); - - PackageEntries entries{ - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list_fail, '\n') }, - }; - - // "2\nstash " + block3_hash + " 2,2,3" - std::string last_command_content = - "2\n" + transfer_list_fail[TransferList::kTransferListHeaderLines + 2]; - - RunBlockImageUpdate(false, entries, image_file_, ""); - - // Expect last_command to contain the last stash command. - std::string last_command_actual; - ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual)); - EXPECT_EQ(last_command_content, last_command_actual); - - std::string updated_contents; - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_contents)); - ASSERT_EQ(block1 + block1 + block3, updated_contents); - - // "Resume" the update. Expect the first 'move' to be skipped but the second 'move' to be - // executed. Note that we intentionally reset the image file. - entries["transfer_list"] = android::base::Join(transfer_list_continue, '\n'); - ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_)); - RunBlockImageUpdate(false, entries, image_file_, "t"); - - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_contents)); - ASSERT_EQ(block1 + block2 + block1, updated_contents); -} - -TEST_F(UpdaterTest, last_command_update_unresumable) { - std::string block1(4096, '1'); - std::string block2(4096, '2'); - std::string block1_hash = GetSha1(block1); - std::string block2_hash = GetSha1(block2); - - // Construct an unresumable update with source blocks mismatch. - std::vector transfer_list_unresumable{ - // clang-format off - "4", - "2", - "0", - "2", - "stash " + block1_hash + " 2,0,1", - "move " + block2_hash + " 2,1,2 1 2,0,1", - // clang-format on - }; - - PackageEntries entries{ - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list_unresumable, '\n') }, - }; - - ASSERT_TRUE(android::base::WriteStringToFile(block1 + block1, image_file_)); - - std::string last_command_content = - "0\n" + transfer_list_unresumable[TransferList::kTransferListHeaderLines]; - ASSERT_TRUE(android::base::WriteStringToFile(last_command_content, last_command_file_)); - - RunBlockImageUpdate(false, entries, image_file_, ""); - - // The last_command_file will be deleted if the update encounters an unresumable failure later. - ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); -} - -TEST_F(UpdaterTest, last_command_verify) { - std::string block1(4096, '1'); - std::string block2(4096, '2'); - std::string block3(4096, '3'); - std::string block1_hash = GetSha1(block1); - std::string block2_hash = GetSha1(block2); - std::string block3_hash = GetSha1(block3); - - std::vector transfer_list_verify{ - // clang-format off - "4", - "2", - "0", - "2", - "stash " + block1_hash + " 2,0,1", - "move " + block1_hash + " 2,0,1 1 2,0,1", - "move " + block1_hash + " 2,1,2 1 2,0,1", - "stash " + block3_hash + " 2,2,3", - // clang-format on - }; - - PackageEntries entries{ - { "new_data", "" }, - { "patch_data", "" }, - { "transfer_list", android::base::Join(transfer_list_verify, '\n') }, - }; - - ASSERT_TRUE(android::base::WriteStringToFile(block1 + block1 + block3, image_file_)); - - // Last command: "move " + block1_hash + " 2,1,2 1 2,0,1" - std::string last_command_content = - "2\n" + transfer_list_verify[TransferList::kTransferListHeaderLines + 2]; - - // First run: expect the verification to succeed and the last_command_file is intact. - ASSERT_TRUE(android::base::WriteStringToFile(last_command_content, last_command_file_)); - - RunBlockImageUpdate(true, entries, image_file_, "t"); - - std::string last_command_actual; - ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual)); - EXPECT_EQ(last_command_content, last_command_actual); - - // Second run with a mismatching block image: expect the verification to succeed but - // last_command_file to be deleted; because the target blocks in the last command don't have the - // expected contents for the second move command. - ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_)); - RunBlockImageUpdate(true, entries, image_file_, "t"); - ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); -} - -class ResumableUpdaterTest : public UpdaterTestBase, public testing::TestWithParam { - protected: - void SetUp() override { - UpdaterTestBase::SetUp(); - // Enable a special command "abort" to simulate interruption. - Command::abort_allowed_ = true; - index_ = GetParam(); - } - - void TearDown() override { - UpdaterTestBase::TearDown(); - } - - size_t index_; -}; - -static std::string g_source_image; -static std::string g_target_image; -static PackageEntries g_entries; - -static std::vector GenerateTransferList() { - std::string a(4096, 'a'); - std::string b(4096, 'b'); - std::string c(4096, 'c'); - std::string d(4096, 'd'); - std::string e(4096, 'e'); - std::string f(4096, 'f'); - std::string g(4096, 'g'); - std::string h(4096, 'h'); - std::string i(4096, 'i'); - std::string zero(4096, '\0'); - - std::string a_hash = GetSha1(a); - std::string b_hash = GetSha1(b); - std::string c_hash = GetSha1(c); - std::string e_hash = GetSha1(e); - - auto loc = [](const std::string& range_text) { - std::vector pieces = android::base::Split(range_text, "-"); - size_t left; - size_t right; - if (pieces.size() == 1) { - CHECK(android::base::ParseUint(pieces[0], &left)); - right = left + 1; - } else { - CHECK_EQ(2u, pieces.size()); - CHECK(android::base::ParseUint(pieces[0], &left)); - CHECK(android::base::ParseUint(pieces[1], &right)); - right++; - } - return android::base::StringPrintf("2,%zu,%zu", left, right); - }; - - // patch 1: "b d c" -> "g" - TemporaryFile patch_file_bdc_g; - std::string bdc = b + d + c; - std::string bdc_hash = GetSha1(bdc); - std::string g_hash = GetSha1(g); - CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast(bdc.data()), bdc.size(), - reinterpret_cast(g.data()), g.size(), - patch_file_bdc_g.path, nullptr)); - std::string patch_bdc_g; - CHECK(android::base::ReadFileToString(patch_file_bdc_g.path, &patch_bdc_g)); - - // patch 2: "a b c d" -> "d c b" - TemporaryFile patch_file_abcd_dcb; - std::string abcd = a + b + c + d; - std::string abcd_hash = GetSha1(abcd); - std::string dcb = d + c + b; - std::string dcb_hash = GetSha1(dcb); - CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast(abcd.data()), abcd.size(), - reinterpret_cast(dcb.data()), dcb.size(), - patch_file_abcd_dcb.path, nullptr)); - std::string patch_abcd_dcb; - CHECK(android::base::ReadFileToString(patch_file_abcd_dcb.path, &patch_abcd_dcb)); - - std::vector transfer_list{ - "4", - "10", // total blocks written - "2", // maximum stash entries - "2", // maximum number of stashed blocks - - // a b c d e a b c d e - "stash " + b_hash + " " + loc("1"), - // a b c d e a b c d e [b(1)] - "stash " + c_hash + " " + loc("2"), - // a b c d e a b c d e [b(1)][c(2)] - "new " + loc("1-2"), - // a i h d e a b c d e [b(1)][c(2)] - "zero " + loc("0"), - // 0 i h d e a b c d e [b(1)][c(2)] - - // bsdiff "b d c" (from stash, 3, stash) to get g(3) - android::base::StringPrintf( - "bsdiff 0 %zu %s %s %s 3 %s %s %s:%s %s:%s", - patch_bdc_g.size(), // patch start (0), patch length - bdc_hash.c_str(), // source hash - g_hash.c_str(), // target hash - loc("3").c_str(), // target range - loc("3").c_str(), loc("1").c_str(), // load "d" from block 3, into buffer at offset 1 - b_hash.c_str(), loc("0").c_str(), // load "b" from stash, into buffer at offset 0 - c_hash.c_str(), loc("2").c_str()), // load "c" from stash, into buffer at offset 2 - - // 0 i h g e a b c d e [b(1)][c(2)] - "free " + b_hash, - // 0 i h g e a b c d e [c(2)] - "free " + a_hash, - // 0 i h g e a b c d e - "stash " + a_hash + " " + loc("5"), - // 0 i h g e a b c d e [a(5)] - "move " + e_hash + " " + loc("5") + " 1 " + loc("4"), - // 0 i h g e e b c d e [a(5)] - - // bsdiff "a b c d" (from stash, 6-8) to "d c b" (6-8) - android::base::StringPrintf( // - "bsdiff %zu %zu %s %s %s 4 %s %s %s:%s", - patch_bdc_g.size(), // patch start - patch_bdc_g.size() + patch_abcd_dcb.size(), // patch length - abcd_hash.c_str(), // source hash - dcb_hash.c_str(), // target hash - loc("6-8").c_str(), // target range - loc("6-8").c_str(), // load "b c d" from blocks 6-8 - loc("1-3").c_str(), // into buffer at offset 1-3 - a_hash.c_str(), // load "a" from stash - loc("0").c_str()), // into buffer at offset 0 - - // 0 i h g e e d c b e [a(5)] - "new " + loc("4"), - // 0 i h g f e d c b e [a(5)] - "move " + a_hash + " " + loc("9") + " 1 - " + a_hash + ":" + loc("0"), - // 0 i h g f e d c b a [a(5)] - "free " + a_hash, - // 0 i h g f e d c b a - }; - - std::string new_data = i + h + f; - std::string patch_data = patch_bdc_g + patch_abcd_dcb; - - g_entries = { - { "new_data", new_data }, - { "patch_data", patch_data }, - }; - g_source_image = a + b + c + d + e + a + b + c + d + e; - g_target_image = zero + i + h + g + f + e + d + c + b + a; - - return transfer_list; -} - -static const std::vector g_transfer_list = GenerateTransferList(); - -INSTANTIATE_TEST_CASE_P(InterruptAfterEachCommand, ResumableUpdaterTest, - ::testing::Range(static_cast(0), - g_transfer_list.size() - - TransferList::kTransferListHeaderLines)); - -TEST_P(ResumableUpdaterTest, InterruptVerifyResume) { - ASSERT_TRUE(android::base::WriteStringToFile(g_source_image, image_file_)); - - LOG(INFO) << "Interrupting at line " << index_ << " (" - << g_transfer_list[TransferList::kTransferListHeaderLines + index_] << ")"; - - std::vector transfer_list_copy{ g_transfer_list }; - transfer_list_copy[TransferList::kTransferListHeaderLines + index_] = "abort"; - - g_entries["transfer_list"] = android::base::Join(transfer_list_copy, '\n'); - - // Run update that's expected to fail. - RunBlockImageUpdate(false, g_entries, image_file_, ""); - - std::string last_command_expected; - - // Assert the last_command_file. - if (index_ == 0) { - ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); - } else { - last_command_expected = std::to_string(index_ - 1) + "\n" + - g_transfer_list[TransferList::kTransferListHeaderLines + index_ - 1]; - std::string last_command_actual; - ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual)); - ASSERT_EQ(last_command_expected, last_command_actual); - } - - g_entries["transfer_list"] = android::base::Join(g_transfer_list, '\n'); - - // Resume the interrupted update, by doing verification first. - RunBlockImageUpdate(true, g_entries, image_file_, "t"); - - // last_command_file should remain intact. - if (index_ == 0) { - ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); - } else { - std::string last_command_actual; - ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual)); - ASSERT_EQ(last_command_expected, last_command_actual); - } - - // Resume the update. - RunBlockImageUpdate(false, g_entries, image_file_, "t"); - - // last_command_file should be gone after successful update. - ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK)); - - std::string updated_image_actual; - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_image_actual)); - ASSERT_EQ(g_target_image, updated_image_actual); -} diff --git a/updater/Android.bp b/updater/Android.bp deleted file mode 100644 index 4fc3c494..00000000 --- a/updater/Android.bp +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "bootable_recovery_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["bootable_recovery_license"], -} - -cc_defaults { - name: "libupdater_static_libs", - - static_libs: [ - "libapplypatch", - "libbootloader_message", - "libbspatch", - "libedify", - "libotautil", - "libext4_utils", - "libdm", - "libfec", - "libfec_rs", - "libavb", - "libverity_tree", - "liblog", - "liblp", - "libselinux", - "libsparse", - "libsquashfs_utils", - "libbrotli", - "libbz", - "libziparchive", - "libz_stable", - "libbase", - "libcrypto_utils", - "libcutils", - "libutils", - ], - header_libs: [ - "libgtest_prod_headers", - ], -} - -cc_defaults { - name: "libupdater_defaults", - - defaults: [ - "recovery_defaults", - "libupdater_static_libs", - ], - - shared_libs: [ - "libcrypto", - ], -} - -cc_defaults { - name: "libupdater_device_defaults", - - static_libs: [ - "libfs_mgr", - "libtune2fs", - - "libext2_com_err", - "libext2_blkid", - "libext2_quota", - "libext2_uuid", - "libext2_e2p", - "libext2fs", - ], -} - -cc_library_static { - name: "libupdater_core", - - host_supported: true, - - defaults: [ - "recovery_defaults", - "libupdater_defaults", - ], - - srcs: [ - "blockimg.cpp", - "commands.cpp", - "install.cpp", - "mounts.cpp", - "updater.cpp", - ], - - target: { - darwin: { - enabled: false, - }, - }, - - export_include_dirs: [ - "include", - ], -} - -cc_library_static { - name: "libupdater_device", - - defaults: [ - "recovery_defaults", - "libupdater_defaults", - "libupdater_device_defaults", - ], - - srcs: [ - "dynamic_partitions.cpp", - "updater_runtime.cpp", - "updater_runtime_dynamic_partitions.cpp", - ], - - static_libs: [ - "libupdater_core", - ], - - include_dirs: [ - "external/e2fsprogs/misc", - ], - - export_include_dirs: [ - "include", - ], -} - -cc_library_host_static { - name: "libupdater_host", - - defaults: [ - "recovery_defaults", - "libupdater_defaults", - ], - - srcs: [ - "build_info.cpp", - "dynamic_partitions.cpp", - "simulator_runtime.cpp", - "target_files.cpp", - ], - - static_libs: [ - "libupdater_core", - "libfstab", - "libc++fs", - ], - - target: { - darwin: { - enabled: false, - }, - }, - - export_include_dirs: [ - "include", - ], -} - -cc_binary_host { - name: "update_host_simulator", - defaults: ["libupdater_static_libs"], - - srcs: ["update_simulator_main.cpp"], - - cflags: [ - "-Wall", - "-Werror", - ], - - static_libs: [ - "libupdater_host", - "libupdater_core", - "libcrypto_static", - "libfstab", - "libc++fs", - ], - - target: { - darwin: { - enabled: false, - }, - }, -} diff --git a/updater/Android.mk b/updater/Android.mk deleted file mode 100644 index 2fd56397..00000000 --- a/updater/Android.mk +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright 2009 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -LOCAL_PATH := $(call my-dir) - -tune2fs_static_libraries := \ - libext2_com_err \ - libext2_blkid \ - libext2_quota \ - libext2_uuid \ - libext2_e2p \ - libext2fs - -updater_common_static_libraries := \ - libapplypatch \ - libbootloader_message \ - libbspatch \ - libedify \ - libotautil \ - libext4_utils \ - libdm \ - libfec \ - libfec_rs \ - libavb \ - libverity_tree \ - liblog \ - liblp \ - libselinux \ - libsparse \ - libsquashfs_utils \ - libbrotli \ - libbz \ - libziparchive \ - libz_stable \ - libbase \ - libcrypto_static \ - libcrypto_utils \ - libcutils \ - libutils - - -# Each library in TARGET_RECOVERY_UPDATER_LIBS should have a function -# named "Register_()". Here we emit a little C function that -# gets #included by updater.cpp. It calls all those registration -# functions. -# $(1): the path to the register.inc file -# $(2): a list of TARGET_RECOVERY_UPDATER_LIBS -define generate-register-inc - $(hide) mkdir -p $(dir $(1)) - $(hide) echo "" > $(1) - $(hide) $(foreach lib,$(2),echo "extern void Register_$(lib)(void);" >> $(1);) - $(hide) echo "void RegisterDeviceExtensions() {" >> $(1) - $(hide) $(foreach lib,$(2),echo " Register_$(lib)();" >> $(1);) - $(hide) echo "}" >> $(1) -endef - - -# updater (static executable) -# =============================== -include $(CLEAR_VARS) - -LOCAL_MODULE := updater -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../NOTICE - -LOCAL_SRC_FILES := \ - updater_main.cpp - -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/include - -LOCAL_CFLAGS := \ - -Wall \ - -Werror - -LOCAL_STATIC_LIBRARIES := \ - libupdater_device \ - libupdater_core \ - $(TARGET_RECOVERY_UPDATER_LIBS) \ - $(TARGET_RECOVERY_UPDATER_EXTRA_LIBS) \ - $(updater_common_static_libraries) \ - libfs_mgr \ - libtune2fs \ - $(tune2fs_static_libraries) - -LOCAL_HEADER_LIBRARIES := libgtest_prod_headers - -LOCAL_MODULE_CLASS := EXECUTABLES -inc := $(call local-generated-sources-dir)/register.inc - -# Devices can also add libraries to TARGET_RECOVERY_UPDATER_EXTRA_LIBS. -# These libs are also linked in with updater, but we don't try to call -# any sort of registration function for these. Use this variable for -# any subsidiary static libraries required for your registered -# extension libs. -$(inc) : libs := $(TARGET_RECOVERY_UPDATER_LIBS) -$(inc) : - $(call generate-register-inc,$@,$(libs)) - -LOCAL_GENERATED_SOURCES := $(inc) - -inc := - -LOCAL_FORCE_STATIC_EXECUTABLE := true - -include $(BUILD_EXECUTABLE) diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp deleted file mode 100644 index b29aa8ce..00000000 --- a/updater/blockimg.cpp +++ /dev/null @@ -1,2303 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "edify/expr.h" -#include "edify/updater_interface.h" -#include "otautil/dirutil.h" -#include "otautil/error_code.h" -#include "otautil/paths.h" -#include "otautil/print_sha1.h" -#include "otautil/rangeset.h" -#include "private/commands.h" -#include "updater/install.h" - -#ifdef __ANDROID__ -#include -// Set this to 0 to interpret 'erase' transfers to mean do a BLKDISCARD ioctl (the normal behavior). -// Set to 1 to interpret erase to mean fill the region with zeroes. -#define DEBUG_ERASE 0 -#else -#define DEBUG_ERASE 1 -#define AID_SYSTEM -1 -#endif // __ANDROID__ - -static constexpr size_t BLOCKSIZE = 4096; -static constexpr mode_t STASH_DIRECTORY_MODE = 0700; -static constexpr mode_t STASH_FILE_MODE = 0600; -static constexpr mode_t MARKER_DIRECTORY_MODE = 0700; - -static CauseCode failure_type = kNoCause; -static bool is_retry = false; -static std::unordered_map stash_map; - -static void DeleteLastCommandFile() { - const std::string& last_command_file = Paths::Get().last_command_file(); - if (unlink(last_command_file.c_str()) == -1 && errno != ENOENT) { - PLOG(ERROR) << "Failed to unlink: " << last_command_file; - } -} - -// Parse the last command index of the last update and save the result to |last_command_index|. -// Return true if we successfully read the index. -static bool ParseLastCommandFile(size_t* last_command_index) { - const std::string& last_command_file = Paths::Get().last_command_file(); - android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(last_command_file.c_str(), O_RDONLY))); - if (fd == -1) { - if (errno != ENOENT) { - PLOG(ERROR) << "Failed to open " << last_command_file; - return false; - } - - LOG(INFO) << last_command_file << " doesn't exist."; - return false; - } - - // Now that the last_command file exists, parse the last command index of previous update. - std::string content; - if (!android::base::ReadFdToString(fd.get(), &content)) { - LOG(ERROR) << "Failed to read: " << last_command_file; - return false; - } - - std::vector lines = android::base::Split(android::base::Trim(content), "\n"); - if (lines.size() != 2) { - LOG(ERROR) << "Unexpected line counts in last command file: " << content; - return false; - } - - if (!android::base::ParseUint(lines[0], last_command_index)) { - LOG(ERROR) << "Failed to parse integer in: " << lines[0]; - return false; - } - - return true; -} - -static bool FsyncDir(const std::string& dirname) { - android::base::unique_fd dfd(TEMP_FAILURE_RETRY(open(dirname.c_str(), O_RDONLY | O_DIRECTORY))); - if (dfd == -1) { - failure_type = errno == EIO ? kEioFailure : kFileOpenFailure; - PLOG(ERROR) << "Failed to open " << dirname; - return false; - } - if (fsync(dfd) == -1) { - failure_type = errno == EIO ? kEioFailure : kFsyncFailure; - PLOG(ERROR) << "Failed to fsync " << dirname; - return false; - } - return true; -} - -// Update the last executed command index in the last_command_file. -static bool UpdateLastCommandIndex(size_t command_index, const std::string& command_string) { - const std::string& last_command_file = Paths::Get().last_command_file(); - std::string last_command_tmp = last_command_file + ".tmp"; - std::string content = std::to_string(command_index) + "\n" + command_string; - android::base::unique_fd wfd( - TEMP_FAILURE_RETRY(open(last_command_tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0660))); - if (wfd == -1 || !android::base::WriteStringToFd(content, wfd)) { - PLOG(ERROR) << "Failed to update last command"; - return false; - } - - if (fsync(wfd) == -1) { - PLOG(ERROR) << "Failed to fsync " << last_command_tmp; - return false; - } - - if (chown(last_command_tmp.c_str(), AID_SYSTEM, AID_SYSTEM) == -1) { - PLOG(ERROR) << "Failed to change owner for " << last_command_tmp; - return false; - } - - if (rename(last_command_tmp.c_str(), last_command_file.c_str()) == -1) { - PLOG(ERROR) << "Failed to rename" << last_command_tmp; - return false; - } - - if (!FsyncDir(android::base::Dirname(last_command_file))) { - return false; - } - - return true; -} - -bool SetUpdatedMarker(const std::string& marker) { - auto dirname = android::base::Dirname(marker); - auto res = mkdir(dirname.c_str(), MARKER_DIRECTORY_MODE); - if (res == -1 && errno != EEXIST) { - PLOG(ERROR) << "Failed to create directory for marker: " << dirname; - return false; - } - - if (!android::base::WriteStringToFile("", marker)) { - PLOG(ERROR) << "Failed to write to marker file " << marker; - return false; - } - if (!FsyncDir(dirname)) { - return false; - } - LOG(INFO) << "Wrote updated marker to " << marker; - return true; -} - -static bool discard_blocks(int fd, off64_t offset, uint64_t size, bool force = false) { - // Don't discard blocks unless the update is a retry run or force == true - if (!is_retry && !force) { - return true; - } - - uint64_t args[2] = { static_cast(offset), size }; - if (ioctl(fd, BLKDISCARD, &args) == -1) { - // On devices that does not support BLKDISCARD, ignore the error. - if (errno == EOPNOTSUPP) { - return true; - } - PLOG(ERROR) << "BLKDISCARD ioctl failed"; - return false; - } - return true; -} - -static bool check_lseek(int fd, off64_t offset, int whence) { - off64_t rc = TEMP_FAILURE_RETRY(lseek64(fd, offset, whence)); - if (rc == -1) { - failure_type = kLseekFailure; - PLOG(ERROR) << "lseek64 failed"; - return false; - } - return true; -} - -static void allocate(size_t size, std::vector* buffer) { - // If the buffer's big enough, reuse it. - if (size <= buffer->size()) return; - buffer->resize(size); -} - -/** - * RangeSinkWriter reads data from the given FD, and writes them to the destination specified by the - * given RangeSet. - */ -class RangeSinkWriter { - public: - RangeSinkWriter(int fd, const RangeSet& tgt) - : fd_(fd), - tgt_(tgt), - next_range_(0), - current_range_left_(0), - bytes_written_(0) { - CHECK_NE(tgt.size(), static_cast(0)); - }; - - bool Finished() const { - 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"; - return 0; - } - - size_t written = 0; - while (size > 0) { - // Move to the next range as needed. - if (!SeekToOutputRange()) { - break; - } - - size_t write_now = size; - if (current_range_left_ < write_now) { - write_now = current_range_left_; - } - - if (!android::base::WriteFully(fd_, data, write_now)) { - failure_type = errno == EIO ? kEioFailure : kFwriteFailure; - PLOG(ERROR) << "Failed to write " << write_now << " bytes of data"; - break; - } - - data += write_now; - size -= write_now; - - current_range_left_ -= write_now; - written += write_now; - } - - bytes_written_ += written; - return written; - } - - size_t BytesWritten() const { - return bytes_written_; - } - - private: - // 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(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 ranges for the data. - const RangeSet& tgt_; - // The next range that we should write to. - size_t next_range_; - // The number of bytes to write before moving to the next range. - size_t current_range_left_; - // Total bytes written by the writer. - size_t bytes_written_; -}; - -/** - * All of the data for all the 'new' transfers is contained in one file in the update package, - * concatenated together in the order in which transfers.list will need it. We want to stream it out - * of the archive (it's compressed) without writing it to a temp file, but we can't write each - * section until it's that transfer's turn to go. - * - * To achieve this, we expand the new data from the archive in a background thread, and block that - * threads 'receive uncompressed data' function until the main thread has reached a point where we - * want some new data to be written. We signal the background thread with the destination for the - * data and block the main thread, waiting for the background thread to complete writing that - * section. Then it signals the main thread to wake up and goes back to blocking waiting for a - * transfer. - * - * NewThreadInfo is the struct used to pass information back and forth between the two threads. When - * the main thread wants some data written, it sets writer to the destination location and signals - * the condition. When the background thread is done writing, it clears writer and signals the - * condition again. - */ -struct NewThreadInfo { - ZipArchiveHandle za; - ZipEntry64 entry{}; - bool brotli_compressed; - - std::unique_ptr writer; - BrotliDecoderState* brotli_decoder_state; - bool receiver_available; - - pthread_mutex_t mu; - pthread_cond_t cv; -}; - -static bool receive_new_data(const uint8_t* data, size_t size, void* cookie) { - NewThreadInfo* nti = static_cast(cookie); - - while (size > 0) { - // Wait for nti->writer to be non-null, indicating some of this data is wanted. - pthread_mutex_lock(&nti->mu); - while (nti->writer == nullptr) { - // End the new data receiver if we encounter an error when performing block image update. - if (!nti->receiver_available) { - pthread_mutex_unlock(&nti->mu); - return false; - } - 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 write_now = std::min(size, nti->writer->AvailableSpace()); - if (nti->writer->Write(data, write_now) != write_now) { - LOG(ERROR) << "Failed to write " << write_now << " bytes."; - return false; - } - - data += write_now; - size -= write_now; - - if (nti->writer->Finished()) { - // We have written all the bytes desired by this writer. - - pthread_mutex_lock(&nti->mu); - nti->writer = nullptr; - pthread_cond_broadcast(&nti->cv); - pthread_mutex_unlock(&nti->mu); - } - } - - return true; -} - -static bool receive_brotli_new_data(const uint8_t* data, size_t size, void* cookie) { - NewThreadInfo* nti = static_cast(cookie); - - while (size > 0 || BrotliDecoderHasMoreOutput(nti->brotli_decoder_state)) { - // Wait for nti->writer to be non-null, indicating some of this data is wanted. - pthread_mutex_lock(&nti->mu); - while (nti->writer == nullptr) { - // End the receiver if we encounter an error when performing block image update. - if (!nti->receiver_available) { - pthread_mutex_unlock(&nti->mu); - return false; - } - 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(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(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) { - pthread_cond_broadcast(&nti->cv); - } - pthread_mutex_unlock(&nti->mu); - return nullptr; -} - -static int ReadBlocks(const RangeSet& src, std::vector* buffer, int fd) { - size_t p = 0; - for (const auto& [begin, end] : src) { - if (!check_lseek(fd, static_cast(begin) * BLOCKSIZE, SEEK_SET)) { - return -1; - } - - size_t size = (end - begin) * BLOCKSIZE; - if (!android::base::ReadFully(fd, buffer->data() + p, size)) { - failure_type = errno == EIO ? kEioFailure : kFreadFailure; - PLOG(ERROR) << "Failed to read " << size << " bytes of data"; - return -1; - } - - p += size; - } - - return 0; -} - -static int WriteBlocks(const RangeSet& tgt, const std::vector& buffer, int fd) { - size_t written = 0; - for (const auto& [begin, end] : tgt) { - off64_t offset = static_cast(begin) * BLOCKSIZE; - size_t size = (end - begin) * BLOCKSIZE; - if (!discard_blocks(fd, offset, size)) { - return -1; - } - - if (!check_lseek(fd, offset, SEEK_SET)) { - return -1; - } - - if (!android::base::WriteFully(fd, buffer.data() + written, size)) { - failure_type = errno == EIO ? kEioFailure : kFwriteFailure; - PLOG(ERROR) << "Failed to write " << size << " bytes of data"; - return -1; - } - - written += size; - } - - return 0; -} - -// Parameters for transfer list command functions -struct CommandParameters { - std::vector tokens; - size_t cpos; - std::string cmdname; - std::string cmdline; - std::string freestash; - std::string stashbase; - bool canwrite; - int createdstash; - android::base::unique_fd fd; - bool foundwrites; - bool isunresumable; - int version; - size_t written; - size_t stashed; - NewThreadInfo nti; - pthread_t thread; - std::vector buffer; - uint8_t* patch_start; - bool target_verified; // The target blocks have expected contents already. -}; - -// Print the hash in hex for corrupted source blocks (excluding the stashed blocks which is -// handled separately). -static void PrintHashForCorruptedSourceBlocks(const CommandParameters& params, - const std::vector& buffer) { - LOG(INFO) << "unexpected contents of source blocks in cmd:\n" << params.cmdline; - CHECK(params.tokens[0] == "move" || params.tokens[0] == "bsdiff" || - params.tokens[0] == "imgdiff"); - - size_t pos = 0; - // Command example: - // move [ ] - // bsdiff - // [ ] - if (params.tokens[0] == "move") { - // src_range for move starts at the 4th position. - if (params.tokens.size() < 5) { - LOG(ERROR) << "failed to parse source range in cmd:\n" << params.cmdline; - return; - } - pos = 4; - } else { - // src_range for diff starts at the 7th position. - if (params.tokens.size() < 8) { - LOG(ERROR) << "failed to parse source range in cmd:\n" << params.cmdline; - return; - } - pos = 7; - } - - // Source blocks in stash only, no work to do. - if (params.tokens[pos] == "-") { - return; - } - - RangeSet src = RangeSet::Parse(params.tokens[pos++]); - if (!src) { - LOG(ERROR) << "Failed to parse range in " << params.cmdline; - return; - } - - RangeSet locs; - // If there's no stashed blocks, content in the buffer is consecutive and has the same - // order as the source blocks. - if (pos == params.tokens.size()) { - locs = RangeSet(std::vector{ Range{ 0, src.blocks() } }); - } else { - // Otherwise, the next token is the offset of the source blocks in the target range. - // Example: for the tokens <4,63946,63947,63948,63979> <4,6,7,8,39> ; - // We want to print SHA-1 for the data in buffer[6], buffer[8], buffer[9] ... buffer[38]; - // this corresponds to the 32 src blocks #63946, #63948, #63949 ... #63978. - locs = RangeSet::Parse(params.tokens[pos++]); - CHECK_EQ(src.blocks(), locs.blocks()); - } - - LOG(INFO) << "printing hash in hex for " << src.blocks() << " source blocks"; - for (size_t i = 0; i < src.blocks(); i++) { - size_t block_num = src.GetBlockNumber(i); - size_t buffer_index = locs.GetBlockNumber(i); - CHECK_LE((buffer_index + 1) * BLOCKSIZE, buffer.size()); - - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(buffer.data() + buffer_index * BLOCKSIZE, BLOCKSIZE, digest); - std::string hexdigest = print_sha1(digest); - LOG(INFO) << " block number: " << block_num << ", SHA-1: " << hexdigest; - } -} - -// If the calculated hash for the whole stash doesn't match the stash id, print the SHA-1 -// in hex for each block. -static void PrintHashForCorruptedStashedBlocks(const std::string& id, - const std::vector& buffer, - const RangeSet& src) { - LOG(INFO) << "printing hash in hex for stash_id: " << id; - CHECK_EQ(src.blocks() * BLOCKSIZE, buffer.size()); - - for (size_t i = 0; i < src.blocks(); i++) { - size_t block_num = src.GetBlockNumber(i); - - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(buffer.data() + i * BLOCKSIZE, BLOCKSIZE, digest); - std::string hexdigest = print_sha1(digest); - LOG(INFO) << " block number: " << block_num << ", SHA-1: " << hexdigest; - } -} - -// If the stash file doesn't exist, read the source blocks this stash contains and print the -// SHA-1 for these blocks. -static void PrintHashForMissingStashedBlocks(const std::string& id, int fd) { - if (stash_map.find(id) == stash_map.end()) { - LOG(ERROR) << "No stash saved for id: " << id; - return; - } - - LOG(INFO) << "print hash in hex for source blocks in missing stash: " << id; - const RangeSet& src = stash_map[id]; - std::vector buffer(src.blocks() * BLOCKSIZE); - if (ReadBlocks(src, &buffer, fd) == -1) { - LOG(ERROR) << "failed to read source blocks for stash: " << id; - return; - } - PrintHashForCorruptedStashedBlocks(id, buffer, src); -} - -static int VerifyBlocks(const std::string& expected, const std::vector& buffer, - const size_t blocks, bool printerror) { - uint8_t digest[SHA_DIGEST_LENGTH]; - const uint8_t* data = buffer.data(); - - SHA1(data, blocks * BLOCKSIZE, digest); - - std::string hexdigest = print_sha1(digest); - - if (hexdigest != expected) { - if (printerror) { - LOG(ERROR) << "failed to verify blocks (expected " << expected << ", read " << hexdigest - << ")"; - } - return -1; - } - - return 0; -} - -static std::string GetStashFileName(const std::string& base, const std::string& id, - const std::string& postfix) { - if (base.empty()) { - return ""; - } - std::string filename = Paths::Get().stash_directory_base() + "/" + base; - if (id.empty() && postfix.empty()) { - return filename; - } - return filename + "/" + id + postfix; -} - -// Does a best effort enumeration of stash files. Ignores possible non-file items in the stash -// directory and continues despite of errors. Calls the 'callback' function for each file. -static void EnumerateStash(const std::string& dirname, - const std::function& callback) { - if (dirname.empty()) return; - - std::unique_ptr directory(opendir(dirname.c_str()), closedir); - - if (directory == nullptr) { - if (errno != ENOENT) { - PLOG(ERROR) << "opendir \"" << dirname << "\" failed"; - } - return; - } - - dirent* item; - while ((item = readdir(directory.get())) != nullptr) { - if (item->d_type != DT_REG) continue; - callback(dirname + "/" + item->d_name); - } -} - -// Deletes the stash directory and all files in it. Assumes that it only -// contains files. There is nothing we can do about unlikely, but possible -// errors, so they are merely logged. -static void DeleteFile(const std::string& fn) { - if (fn.empty()) return; - - LOG(INFO) << "deleting " << fn; - - if (unlink(fn.c_str()) == -1 && errno != ENOENT) { - PLOG(ERROR) << "unlink \"" << fn << "\" failed"; - } -} - -static void DeleteStash(const std::string& base) { - if (base.empty()) return; - - LOG(INFO) << "deleting stash " << base; - - std::string dirname = GetStashFileName(base, "", ""); - EnumerateStash(dirname, DeleteFile); - - if (rmdir(dirname.c_str()) == -1) { - if (errno != ENOENT && errno != ENOTDIR) { - PLOG(ERROR) << "rmdir \"" << dirname << "\" failed"; - } - } -} - -static int LoadStash(const CommandParameters& params, const std::string& id, bool verify, - std::vector* buffer, bool printnoent) { - // In verify mode, if source range_set was saved for the given hash, check contents in the source - // blocks first. If the check fails, search for the stashed files on /cache as usual. - if (!params.canwrite) { - if (stash_map.find(id) != stash_map.end()) { - const RangeSet& src = stash_map[id]; - allocate(src.blocks() * BLOCKSIZE, buffer); - - if (ReadBlocks(src, buffer, params.fd) == -1) { - LOG(ERROR) << "failed to read source blocks in stash map."; - return -1; - } - if (VerifyBlocks(id, *buffer, src.blocks(), true) != 0) { - LOG(ERROR) << "failed to verify loaded source blocks in stash map."; - if (!is_retry) { - PrintHashForCorruptedStashedBlocks(id, *buffer, src); - } - return -1; - } - return 0; - } - } - - std::string fn = GetStashFileName(params.stashbase, id, ""); - - struct stat sb; - if (stat(fn.c_str(), &sb) == -1) { - if (errno != ENOENT || printnoent) { - PLOG(ERROR) << "stat \"" << fn << "\" failed"; - PrintHashForMissingStashedBlocks(id, params.fd); - } - return -1; - } - - LOG(INFO) << " loading " << fn; - - if ((sb.st_size % BLOCKSIZE) != 0) { - LOG(ERROR) << fn << " size " << sb.st_size << " not multiple of block size " << BLOCKSIZE; - return -1; - } - - android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(fn.c_str(), O_RDONLY))); - if (fd == -1) { - failure_type = errno == EIO ? kEioFailure : kFileOpenFailure; - PLOG(ERROR) << "open \"" << fn << "\" failed"; - return -1; - } - - allocate(sb.st_size, buffer); - - if (!android::base::ReadFully(fd, buffer->data(), sb.st_size)) { - failure_type = errno == EIO ? kEioFailure : kFreadFailure; - PLOG(ERROR) << "Failed to read " << sb.st_size << " bytes of data"; - return -1; - } - - size_t blocks = sb.st_size / BLOCKSIZE; - if (verify && VerifyBlocks(id, *buffer, blocks, true) != 0) { - LOG(ERROR) << "unexpected contents in " << fn; - if (stash_map.find(id) == stash_map.end()) { - LOG(ERROR) << "failed to find source blocks number for stash " << id - << " when executing command: " << params.cmdname; - } else { - const RangeSet& src = stash_map[id]; - PrintHashForCorruptedStashedBlocks(id, *buffer, src); - } - DeleteFile(fn); - return -1; - } - - return 0; -} - -static int WriteStash(const std::string& base, const std::string& id, int blocks, - const std::vector& buffer, bool checkspace, bool* exists) { - if (base.empty()) { - return -1; - } - - if (checkspace && !CheckAndFreeSpaceOnCache(blocks * BLOCKSIZE)) { - LOG(ERROR) << "not enough space to write stash"; - return -1; - } - - std::string fn = GetStashFileName(base, id, ".partial"); - std::string cn = GetStashFileName(base, id, ""); - - if (exists) { - struct stat sb; - int res = stat(cn.c_str(), &sb); - - if (res == 0) { - // The file already exists and since the name is the hash of the contents, - // it's safe to assume the contents are identical (accidental hash collisions - // are unlikely) - LOG(INFO) << " skipping " << blocks << " existing blocks in " << cn; - *exists = true; - return 0; - } - - *exists = false; - } - - LOG(INFO) << " writing " << blocks << " blocks to " << cn; - - android::base::unique_fd fd( - TEMP_FAILURE_RETRY(open(fn.c_str(), O_WRONLY | O_CREAT | O_TRUNC, STASH_FILE_MODE))); - if (fd == -1) { - failure_type = errno == EIO ? kEioFailure : kFileOpenFailure; - PLOG(ERROR) << "failed to create \"" << fn << "\""; - return -1; - } - - if (fchown(fd, AID_SYSTEM, AID_SYSTEM) != 0) { // system user - PLOG(ERROR) << "failed to chown \"" << fn << "\""; - return -1; - } - - if (!android::base::WriteFully(fd, buffer.data(), blocks * BLOCKSIZE)) { - failure_type = errno == EIO ? kEioFailure : kFwriteFailure; - PLOG(ERROR) << "Failed to write " << blocks * BLOCKSIZE << " bytes of data"; - return -1; - } - - if (fsync(fd) == -1) { - failure_type = errno == EIO ? kEioFailure : kFsyncFailure; - PLOG(ERROR) << "fsync \"" << fn << "\" failed"; - return -1; - } - - if (rename(fn.c_str(), cn.c_str()) == -1) { - PLOG(ERROR) << "rename(\"" << fn << "\", \"" << cn << "\") failed"; - return -1; - } - - std::string dname = GetStashFileName(base, "", ""); - if (!FsyncDir(dname)) { - return -1; - } - - return 0; -} - -// Creates a directory for storing stash files and checks if the /cache partition -// hash enough space for the expected amount of blocks we need to store. Returns -// >0 if we created the directory, zero if it existed already, and <0 of failure. -static int CreateStash(State* state, size_t maxblocks, const std::string& base) { - std::string dirname = GetStashFileName(base, "", ""); - struct stat sb; - int res = stat(dirname.c_str(), &sb); - if (res == -1 && errno != ENOENT) { - ErrorAbort(state, kStashCreationFailure, "stat \"%s\" failed: %s", dirname.c_str(), - strerror(errno)); - return -1; - } - - size_t max_stash_size = maxblocks * BLOCKSIZE; - if (res == -1) { - LOG(INFO) << "creating stash " << dirname; - res = mkdir_recursively(dirname, STASH_DIRECTORY_MODE, false, nullptr); - - if (res != 0) { - ErrorAbort(state, kStashCreationFailure, "mkdir \"%s\" failed: %s", dirname.c_str(), - strerror(errno)); - return -1; - } - - if (chown(dirname.c_str(), AID_SYSTEM, AID_SYSTEM) != 0) { // system user - ErrorAbort(state, kStashCreationFailure, "chown \"%s\" failed: %s", dirname.c_str(), - strerror(errno)); - return -1; - } - - if (!CheckAndFreeSpaceOnCache(max_stash_size)) { - ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu needed)", - max_stash_size); - return -1; - } - - return 1; // Created directory - } - - LOG(INFO) << "using existing stash " << dirname; - - // If the directory already exists, calculate the space already allocated to stash files and check - // if there's enough for all required blocks. Delete any partially completed stash files first. - EnumerateStash(dirname, [](const std::string& fn) { - if (android::base::EndsWith(fn, ".partial")) { - DeleteFile(fn); - } - }); - - size_t existing = 0; - EnumerateStash(dirname, [&existing](const std::string& fn) { - if (fn.empty()) return; - struct stat sb; - if (stat(fn.c_str(), &sb) == -1) { - PLOG(ERROR) << "stat \"" << fn << "\" failed"; - return; - } - existing += static_cast(sb.st_size); - }); - - if (max_stash_size > existing) { - size_t needed = max_stash_size - existing; - if (!CheckAndFreeSpaceOnCache(needed)) { - ErrorAbort(state, kStashCreationFailure, "not enough space for stash (%zu more needed)", - needed); - return -1; - } - } - - return 0; // Using existing directory -} - -static int FreeStash(const std::string& base, const std::string& id) { - if (base.empty() || id.empty()) { - return -1; - } - - DeleteFile(GetStashFileName(base, id, "")); - - return 0; -} - -// Source contains packed data, which we want to move to the locations given in locs in the dest -// buffer. source and dest may be the same buffer. -static void MoveRange(std::vector& dest, const RangeSet& locs, - const std::vector& source) { - const uint8_t* from = source.data(); - uint8_t* to = dest.data(); - size_t start = locs.blocks(); - // Must do the movement backward. - for (auto it = locs.crbegin(); it != locs.crend(); it++) { - size_t blocks = it->second - it->first; - start -= blocks; - memmove(to + (it->first * BLOCKSIZE), from + (start * BLOCKSIZE), blocks * BLOCKSIZE); - } -} - -/** - * We expect to parse the remainder of the parameter tokens as one of: - * - * - * (loads data from source image only) - * - * - <[stash_id:stash_range] ...> - * (loads data from stashes only) - * - * <[stash_id:stash_range] ...> - * (loads data from both source image and stashes) - * - * On return, params.buffer is filled with the loaded source data (rearranged and combined with - * stashed data as necessary). buffer may be reallocated if needed to accommodate the source data. - * tgt is the target RangeSet for detecting overlaps. Any stashes required are loaded using - * LoadStash. - */ -static int LoadSourceBlocks(CommandParameters& params, const RangeSet& tgt, size_t* src_blocks, - bool* overlap) { - CHECK(src_blocks != nullptr); - CHECK(overlap != nullptr); - - // - const std::string& token = params.tokens[params.cpos++]; - if (!android::base::ParseUint(token, src_blocks)) { - LOG(ERROR) << "invalid src_block_count \"" << token << "\""; - return -1; - } - - allocate(*src_blocks * BLOCKSIZE, ¶ms.buffer); - - // "-" or [] - if (params.tokens[params.cpos] == "-") { - // no source ranges, only stashes - params.cpos++; - } else { - RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]); - CHECK(static_cast(src)); - *overlap = src.Overlaps(tgt); - - if (ReadBlocks(src, ¶ms.buffer, params.fd) == -1) { - return -1; - } - - if (params.cpos >= params.tokens.size()) { - // no stashes, only source range - return 0; - } - - RangeSet locs = RangeSet::Parse(params.tokens[params.cpos++]); - CHECK(static_cast(locs)); - MoveRange(params.buffer, locs, params.buffer); - } - - // <[stash_id:stash_range]> - while (params.cpos < params.tokens.size()) { - // Each word is a an index into the stash table, a colon, and then a RangeSet describing where - // in the source block that stashed data should go. - std::vector tokens = android::base::Split(params.tokens[params.cpos++], ":"); - if (tokens.size() != 2) { - LOG(ERROR) << "invalid parameter"; - return -1; - } - - std::vector stash; - if (LoadStash(params, tokens[0], false, &stash, true) == -1) { - // These source blocks will fail verification if used later, but we - // will let the caller decide if this is a fatal failure - LOG(ERROR) << "failed to load stash " << tokens[0]; - continue; - } - - RangeSet locs = RangeSet::Parse(tokens[1]); - CHECK(static_cast(locs)); - MoveRange(params.buffer, locs, stash); - } - - return 0; -} - -/** - * Do a source/target load for move/bsdiff/imgdiff in version 3. - * - * We expect to parse the remainder of the parameter tokens as one of: - * - * - * (loads data from source image only) - * - * - <[stash_id:stash_range] ...> - * (loads data from stashes only) - * - * <[stash_id:stash_range] ...> - * (loads data from both source image and stashes) - * - * 'onehash' tells whether to expect separate source and targe block hashes, or if they are both the - * same and only one hash should be expected. params.isunresumable will be set to true if block - * verification fails in a way that the update cannot be resumed anymore. - * - * If the function is unable to load the necessary blocks or their contents don't match the hashes, - * the return value is -1 and the command should be aborted. - * - * If the return value is 1, the command has already been completed according to the contents of the - * target blocks, and should not be performed again. - * - * If the return value is 0, source blocks have expected content and the command can be performed. - */ -static int LoadSrcTgtVersion3(CommandParameters& params, RangeSet* tgt, size_t* src_blocks, - bool onehash) { - CHECK(src_blocks != nullptr); - - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing source hash"; - return -1; - } - - std::string srchash = params.tokens[params.cpos++]; - std::string tgthash; - - if (onehash) { - tgthash = srchash; - } else { - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing target hash"; - return -1; - } - tgthash = params.tokens[params.cpos++]; - } - - // At least it needs to provide three parameters: , and - // "-"/. - if (params.cpos + 2 >= params.tokens.size()) { - LOG(ERROR) << "invalid parameters"; - return -1; - } - - // - *tgt = RangeSet::Parse(params.tokens[params.cpos++]); - CHECK(static_cast(*tgt)); - - std::vector tgtbuffer(tgt->blocks() * BLOCKSIZE); - if (ReadBlocks(*tgt, &tgtbuffer, params.fd) == -1) { - return -1; - } - - // Return now if target blocks already have expected content. - if (VerifyBlocks(tgthash, tgtbuffer, tgt->blocks(), false) == 0) { - return 1; - } - - // Load source blocks. - bool overlap = false; - if (LoadSourceBlocks(params, *tgt, src_blocks, &overlap) == -1) { - return -1; - } - - if (VerifyBlocks(srchash, params.buffer, *src_blocks, true) == 0) { - // If source and target blocks overlap, stash the source blocks so we can resume from possible - // write errors. In verify mode, we can skip stashing because the source blocks won't be - // overwritten. - if (overlap && params.canwrite) { - LOG(INFO) << "stashing " << *src_blocks << " overlapping blocks to " << srchash; - - bool stash_exists = false; - if (WriteStash(params.stashbase, srchash, *src_blocks, params.buffer, true, - &stash_exists) != 0) { - LOG(ERROR) << "failed to stash overlapping source blocks"; - return -1; - } - - params.stashed += *src_blocks; - // Can be deleted when the write has completed. - if (!stash_exists) { - params.freestash = srchash; - } - } - - // Source blocks have expected content, command can proceed. - return 0; - } - - if (overlap && LoadStash(params, srchash, true, ¶ms.buffer, true) == 0) { - // Overlapping source blocks were previously stashed, command can proceed. We are recovering - // from an interrupted command, so we don't know if the stash can safely be deleted after this - // command. - return 0; - } - - // Valid source data not available, update cannot be resumed. - LOG(ERROR) << "partition has unexpected contents"; - PrintHashForCorruptedSourceBlocks(params, params.buffer); - - params.isunresumable = true; - - return -1; -} - -static int PerformCommandMove(CommandParameters& params) { - size_t blocks = 0; - RangeSet tgt; - int status = LoadSrcTgtVersion3(params, &tgt, &blocks, true); - - if (status == -1) { - LOG(ERROR) << "failed to read blocks for move"; - return -1; - } - - if (status == 0) { - params.foundwrites = true; - } else { - params.target_verified = true; - if (params.foundwrites) { - LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]"; - } - } - - if (params.canwrite) { - if (status == 0) { - LOG(INFO) << " moving " << blocks << " blocks"; - - if (WriteBlocks(tgt, params.buffer, params.fd) == -1) { - return -1; - } - } else { - LOG(INFO) << "skipping " << blocks << " already moved blocks"; - } - } - - if (!params.freestash.empty()) { - FreeStash(params.stashbase, params.freestash); - params.freestash.clear(); - } - - params.written += tgt.blocks(); - - return 0; -} - -static int PerformCommandStash(CommandParameters& params) { - // - if (params.cpos + 1 >= params.tokens.size()) { - LOG(ERROR) << "missing id and/or src range fields in stash command"; - return -1; - } - - const std::string& id = params.tokens[params.cpos++]; - if (LoadStash(params, id, true, ¶ms.buffer, false) == 0) { - // Stash file already exists and has expected contents. Do not read from source again, as the - // source may have been already overwritten during a previous attempt. - return 0; - } - - RangeSet src = RangeSet::Parse(params.tokens[params.cpos++]); - CHECK(static_cast(src)); - - size_t blocks = src.blocks(); - allocate(blocks * BLOCKSIZE, ¶ms.buffer); - if (ReadBlocks(src, ¶ms.buffer, params.fd) == -1) { - return -1; - } - stash_map[id] = src; - - if (VerifyBlocks(id, params.buffer, blocks, true) != 0) { - // Source blocks have unexpected contents. If we actually need this data later, this is an - // unrecoverable error. However, the command that uses the data may have already completed - // previously, so the possible failure will occur during source block verification. - LOG(ERROR) << "failed to load source blocks for stash " << id; - return 0; - } - - // In verify mode, we don't need to stash any blocks. - if (!params.canwrite) { - return 0; - } - - LOG(INFO) << "stashing " << blocks << " blocks to " << id; - int result = WriteStash(params.stashbase, id, blocks, params.buffer, false, nullptr); - if (result == 0) { - params.stashed += blocks; - } - return result; -} - -static int PerformCommandFree(CommandParameters& params) { - // - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing stash id in free command"; - return -1; - } - - const std::string& id = params.tokens[params.cpos++]; - stash_map.erase(id); - - if (params.createdstash || params.canwrite) { - return FreeStash(params.stashbase, id); - } - - return 0; -} - -static int PerformCommandZero(CommandParameters& params) { - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing target blocks for zero"; - return -1; - } - - RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]); - CHECK(static_cast(tgt)); - - LOG(INFO) << " zeroing " << tgt.blocks() << " blocks"; - - allocate(BLOCKSIZE, ¶ms.buffer); - memset(params.buffer.data(), 0, BLOCKSIZE); - - if (params.canwrite) { - for (const auto& [begin, end] : tgt) { - off64_t offset = static_cast(begin) * BLOCKSIZE; - size_t size = (end - begin) * BLOCKSIZE; - if (!discard_blocks(params.fd, offset, size)) { - return -1; - } - - if (!check_lseek(params.fd, offset, SEEK_SET)) { - return -1; - } - - for (size_t j = begin; j < end; ++j) { - if (!android::base::WriteFully(params.fd, params.buffer.data(), BLOCKSIZE)) { - failure_type = errno == EIO ? kEioFailure : kFwriteFailure; - PLOG(ERROR) << "Failed to write " << BLOCKSIZE << " bytes of data"; - return -1; - } - } - } - } - - if (params.cmdname[0] == 'z') { - // Update only for the zero command, as the erase command will call - // this if DEBUG_ERASE is defined. - params.written += tgt.blocks(); - } - - return 0; -} - -static int PerformCommandNew(CommandParameters& params) { - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing target blocks for new"; - return -1; - } - - RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]); - CHECK(static_cast(tgt)); - - if (params.canwrite) { - LOG(INFO) << " writing " << tgt.blocks() << " blocks of new data"; - - pthread_mutex_lock(¶ms.nti.mu); - params.nti.writer = std::make_unique(params.fd, tgt); - pthread_cond_broadcast(¶ms.nti.cv); - - while (params.nti.writer != nullptr) { - if (!params.nti.receiver_available) { - LOG(ERROR) << "missing " << (tgt.blocks() * BLOCKSIZE - params.nti.writer->BytesWritten()) - << " bytes of new data"; - pthread_mutex_unlock(¶ms.nti.mu); - return -1; - } - pthread_cond_wait(¶ms.nti.cv, ¶ms.nti.mu); - } - - pthread_mutex_unlock(¶ms.nti.mu); - } - - params.written += tgt.blocks(); - - return 0; -} - -static int PerformCommandDiff(CommandParameters& params) { - // - if (params.cpos + 1 >= params.tokens.size()) { - LOG(ERROR) << "missing patch offset or length for " << params.cmdname; - return -1; - } - - size_t offset; - if (!android::base::ParseUint(params.tokens[params.cpos++], &offset)) { - LOG(ERROR) << "invalid patch offset"; - return -1; - } - - size_t len; - if (!android::base::ParseUint(params.tokens[params.cpos++], &len)) { - LOG(ERROR) << "invalid patch len"; - return -1; - } - - RangeSet tgt; - size_t blocks = 0; - int status = LoadSrcTgtVersion3(params, &tgt, &blocks, false); - - if (status == -1) { - LOG(ERROR) << "failed to read blocks for diff"; - return -1; - } - - if (status == 0) { - params.foundwrites = true; - } else { - params.target_verified = true; - if (params.foundwrites) { - LOG(WARNING) << "warning: commands executed out of order [" << params.cmdname << "]"; - } - } - - if (params.canwrite) { - if (status == 0) { - LOG(INFO) << "patching " << blocks << " blocks to " << tgt.blocks(); - Value patch_value( - Value::Type::BLOB, - std::string(reinterpret_cast(params.patch_start + offset), len)); - - RangeSinkWriter writer(params.fd, tgt); - if (params.cmdname[0] == 'i') { // imgdiff - if (ApplyImagePatch(params.buffer.data(), blocks * BLOCKSIZE, patch_value, - std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1, - std::placeholders::_2), - nullptr) != 0) { - LOG(ERROR) << "Failed to apply image patch."; - failure_type = kPatchApplicationFailure; - return -1; - } - } else { - if (ApplyBSDiffPatch(params.buffer.data(), blocks * BLOCKSIZE, patch_value, 0, - std::bind(&RangeSinkWriter::Write, &writer, std::placeholders::_1, - std::placeholders::_2)) != 0) { - LOG(ERROR) << "Failed to apply bsdiff patch."; - failure_type = kPatchApplicationFailure; - return -1; - } - } - - // We expect the output of the patcher to fill the tgt ranges exactly. - if (!writer.Finished()) { - LOG(ERROR) << "Failed to fully write target blocks (range sink underrun): Missing " - << writer.AvailableSpace() << " bytes"; - failure_type = kPatchApplicationFailure; - return -1; - } - } else { - LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.blocks() << " [" - << params.cmdline << "]"; - } - } - - if (!params.freestash.empty()) { - FreeStash(params.stashbase, params.freestash); - params.freestash.clear(); - } - - params.written += tgt.blocks(); - - return 0; -} - -static int PerformCommandErase(CommandParameters& params) { - if (DEBUG_ERASE) { - return PerformCommandZero(params); - } - - struct stat sb; - if (fstat(params.fd, &sb) == -1) { - PLOG(ERROR) << "failed to fstat device to erase"; - return -1; - } - - if (!S_ISBLK(sb.st_mode)) { - LOG(ERROR) << "not a block device; skipping erase"; - return -1; - } - - if (params.cpos >= params.tokens.size()) { - LOG(ERROR) << "missing target blocks for erase"; - return -1; - } - - RangeSet tgt = RangeSet::Parse(params.tokens[params.cpos++]); - CHECK(static_cast(tgt)); - - if (params.canwrite) { - LOG(INFO) << " erasing " << tgt.blocks() << " blocks"; - - for (const auto& [begin, end] : tgt) { - off64_t offset = static_cast(begin) * BLOCKSIZE; - size_t size = (end - begin) * BLOCKSIZE; - if (!discard_blocks(params.fd, offset, size, true /* force */)) { - return -1; - } - } - } - - return 0; -} - -static int PerformCommandAbort(CommandParameters&) { - LOG(INFO) << "Aborting as instructed"; - return -1; -} - -// Computes the hash_tree bytes based on the parameters, checks if the root hash of the tree -// matches the expected hash and writes the result to the specified range on the block_device. -// Hash_tree computation arguments: -// hash_tree_ranges -// source_ranges -// hash_algorithm -// salt_hex -// root_hash -static int PerformCommandComputeHashTree(CommandParameters& params) { - if (params.cpos + 5 != params.tokens.size()) { - LOG(ERROR) << "Invaild arguments count in hash computation " << params.cmdline; - return -1; - } - - // Expects the hash_tree data to be contiguous. - RangeSet hash_tree_ranges = RangeSet::Parse(params.tokens[params.cpos++]); - if (!hash_tree_ranges || hash_tree_ranges.size() != 1) { - LOG(ERROR) << "Invalid hash tree ranges in " << params.cmdline; - return -1; - } - - RangeSet source_ranges = RangeSet::Parse(params.tokens[params.cpos++]); - if (!source_ranges) { - LOG(ERROR) << "Invalid source ranges in " << params.cmdline; - return -1; - } - - auto hash_function = HashTreeBuilder::HashFunction(params.tokens[params.cpos++]); - if (hash_function == nullptr) { - LOG(ERROR) << "Invalid hash algorithm in " << params.cmdline; - return -1; - } - - std::vector salt; - std::string salt_hex = params.tokens[params.cpos++]; - if (salt_hex.empty() || !HashTreeBuilder::ParseBytesArrayFromString(salt_hex, &salt)) { - LOG(ERROR) << "Failed to parse salt in " << params.cmdline; - return -1; - } - - std::string expected_root_hash = params.tokens[params.cpos++]; - if (expected_root_hash.empty()) { - LOG(ERROR) << "Invalid root hash in " << params.cmdline; - return -1; - } - - // Starts the hash_tree computation. - HashTreeBuilder builder(BLOCKSIZE, hash_function); - if (!builder.Initialize(static_cast(source_ranges.blocks()) * BLOCKSIZE, salt)) { - LOG(ERROR) << "Failed to initialize hash tree computation, source " << source_ranges.ToString() - << ", salt " << salt_hex; - return -1; - } - - // Iterates through every block in the source_ranges and updates the hash tree structure - // accordingly. - for (const auto& [begin, end] : source_ranges) { - uint8_t buffer[BLOCKSIZE]; - if (!check_lseek(params.fd, static_cast(begin) * BLOCKSIZE, SEEK_SET)) { - PLOG(ERROR) << "Failed to seek to block: " << begin; - return -1; - } - - for (size_t i = begin; i < end; i++) { - if (!android::base::ReadFully(params.fd, buffer, BLOCKSIZE)) { - failure_type = errno == EIO ? kEioFailure : kFreadFailure; - LOG(ERROR) << "Failed to read data in " << begin << ":" << end; - return -1; - } - - if (!builder.Update(reinterpret_cast(buffer), BLOCKSIZE)) { - LOG(ERROR) << "Failed to update hash tree builder"; - return -1; - } - } - } - - if (!builder.BuildHashTree()) { - LOG(ERROR) << "Failed to build hash tree"; - return -1; - } - - std::string root_hash_hex = HashTreeBuilder::BytesArrayToString(builder.root_hash()); - if (root_hash_hex != expected_root_hash) { - LOG(ERROR) << "Root hash of the verity hash tree doesn't match the expected value. Expected: " - << expected_root_hash << ", actual: " << root_hash_hex; - return -1; - } - - uint64_t write_offset = static_cast(hash_tree_ranges.GetBlockNumber(0)) * BLOCKSIZE; - if (params.canwrite && !builder.WriteHashTreeToFd(params.fd, write_offset)) { - LOG(ERROR) << "Failed to write hash tree to output"; - return -1; - } - - // TODO(xunchang) validates the written bytes - - return 0; -} - -using CommandFunction = std::function; - -using CommandMap = std::unordered_map; - -static bool Sha1DevicePath(const std::string& path, uint8_t digest[SHA_DIGEST_LENGTH]) { - auto device_name = android::base::Basename(path); - auto dm_target_name_path = "/sys/block/" + device_name + "/dm/name"; - - struct stat sb; - if (stat(dm_target_name_path.c_str(), &sb) == 0) { - // This is a device mapper target. Use partition name as part of the hash instead. Do not - // include extents as part of the hash, because the size of a partition may be shrunk after - // the patches are applied. - std::string dm_target_name; - if (!android::base::ReadFileToString(dm_target_name_path, &dm_target_name)) { - PLOG(ERROR) << "Cannot read " << dm_target_name_path; - return false; - } - SHA1(reinterpret_cast(dm_target_name.data()), dm_target_name.size(), digest); - return true; - } - - if (errno != ENOENT) { - // This is a device mapper target, but its name cannot be retrieved. - PLOG(ERROR) << "Cannot get dm target name for " << path; - return false; - } - - // This doesn't appear to be a device mapper target, but if its name starts with dm-, something - // else might have gone wrong. - if (android::base::StartsWith(device_name, "dm-")) { - LOG(WARNING) << "Device " << path << " starts with dm- but is not mapped by device-mapper."; - } - - // Stash directory should be different for each partition to avoid conflicts when updating - // multiple partitions at the same time, so we use the hash of the block device name as the base - // directory. - SHA1(reinterpret_cast(path.data()), path.size(), digest); - return true; -} - -static Value* PerformBlockImageUpdate(const char* name, State* state, - const std::vector>& argv, - const CommandMap& command_map, bool dryrun) { - CommandParameters params{}; - stash_map.clear(); - params.canwrite = !dryrun; - - LOG(INFO) << "performing " << (dryrun ? "verification" : "update"); - if (state->is_retry) { - is_retry = true; - LOG(INFO) << "This update is a retry."; - } - if (argv.size() != 4) { - ErrorAbort(state, kArgsParsingFailure, "block_image_update expects 4 arguments, got %zu", - argv.size()); - return StringValue(""); - } - - std::vector> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } - - // args: - // - block device (or file) to modify in-place - // - transfer list (blob) - // - new data stream (filename within package.zip) - // - patch stream (filename within package.zip, must be uncompressed) - const std::unique_ptr& blockdev_filename = args[0]; - const std::unique_ptr& transfer_list_value = args[1]; - const std::unique_ptr& new_data_fn = args[2]; - const std::unique_ptr& patch_data_fn = args[3]; - - if (blockdev_filename->type != Value::Type::STRING) { - ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name); - return StringValue(""); - } - if (transfer_list_value->type != Value::Type::BLOB) { - ErrorAbort(state, kArgsParsingFailure, "transfer_list argument to %s must be blob", name); - return StringValue(""); - } - if (new_data_fn->type != Value::Type::STRING) { - ErrorAbort(state, kArgsParsingFailure, "new_data_fn argument to %s must be string", name); - return StringValue(""); - } - if (patch_data_fn->type != Value::Type::STRING) { - ErrorAbort(state, kArgsParsingFailure, "patch_data_fn argument to %s must be string", name); - return StringValue(""); - } - - auto updater = state->updater; - auto block_device_path = updater->FindBlockDeviceName(blockdev_filename->data); - if (block_device_path.empty()) { - LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name - << " failed."; - return StringValue(""); - } - - ZipArchiveHandle za = updater->GetPackageHandle(); - if (za == nullptr) { - return StringValue(""); - } - - std::string_view path_data(patch_data_fn->data); - ZipEntry64 patch_entry; - if (FindEntry(za, path_data, &patch_entry) != 0) { - LOG(ERROR) << name << "(): no file \"" << patch_data_fn->data << "\" in package"; - return StringValue(""); - } - params.patch_start = updater->GetMappedPackageAddress() + patch_entry.offset; - - std::string_view new_data(new_data_fn->data); - ZipEntry64 new_entry; - if (FindEntry(za, new_data, &new_entry) != 0) { - LOG(ERROR) << name << "(): no file \"" << new_data_fn->data << "\" in package"; - return StringValue(""); - } - - params.fd.reset(TEMP_FAILURE_RETRY(open(block_device_path.c_str(), O_RDWR))); - if (params.fd == -1) { - failure_type = errno == EIO ? kEioFailure : kFileOpenFailure; - PLOG(ERROR) << "open \"" << block_device_path << "\" failed"; - return StringValue(""); - } - - uint8_t digest[SHA_DIGEST_LENGTH]; - if (!Sha1DevicePath(block_device_path, digest)) { - return StringValue(""); - } - params.stashbase = print_sha1(digest); - - // Possibly do return early on retry, by checking the marker. If the update on this partition has - // been finished (but interrupted at a later point), there could be leftover on /cache that would - // fail the no-op retry. - std::string updated_marker = GetStashFileName(params.stashbase + ".UPDATED", "", ""); - if (is_retry) { - struct stat sb; - int result = stat(updated_marker.c_str(), &sb); - if (result == 0) { - LOG(INFO) << "Skipping already updated partition " << block_device_path << " based on marker"; - return StringValue("t"); - } - } else { - // Delete the obsolete marker if any. - std::string err; - if (!android::base::RemoveFileIfExists(updated_marker, &err)) { - LOG(ERROR) << "Failed to remove partition updated marker " << updated_marker << ": " << err; - return StringValue(""); - } - } - - static constexpr size_t kTransferListHeaderLines = 4; - std::vector lines = android::base::Split(transfer_list_value->data, "\n"); - if (lines.size() < kTransferListHeaderLines) { - ErrorAbort(state, kArgsParsingFailure, "too few lines in the transfer list [%zu]", - lines.size()); - return StringValue(""); - } - - // First line in transfer list is the version number. - if (!android::base::ParseInt(lines[0], ¶ms.version, 3, 4)) { - LOG(ERROR) << "unexpected transfer list version [" << lines[0] << "]"; - return StringValue(""); - } - - LOG(INFO) << "blockimg version is " << params.version; - - // Second line in transfer list is the total number of blocks we expect to write. - size_t total_blocks; - if (!android::base::ParseUint(lines[1], &total_blocks)) { - ErrorAbort(state, kArgsParsingFailure, "unexpected block count [%s]", lines[1].c_str()); - return StringValue(""); - } - - if (total_blocks == 0) { - return StringValue("t"); - } - - // Third line is how many stash entries are needed simultaneously. - LOG(INFO) << "maximum stash entries " << lines[2]; - - // Fourth line is the maximum number of blocks that will be stashed simultaneously - size_t stash_max_blocks; - if (!android::base::ParseUint(lines[3], &stash_max_blocks)) { - ErrorAbort(state, kArgsParsingFailure, "unexpected maximum stash blocks [%s]", - lines[3].c_str()); - return StringValue(""); - } - - int res = CreateStash(state, stash_max_blocks, params.stashbase); - if (res == -1) { - return StringValue(""); - } - params.createdstash = res; - - // Set up the new data writer. - 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); - pthread_cond_init(¶ms.nti.cv, nullptr); - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - - int error = pthread_create(¶ms.thread, &attr, unzip_new_data, ¶ms.nti); - if (error != 0) { - LOG(ERROR) << "pthread_create failed: " << strerror(error); - return StringValue(""); - } - } - - // When performing an update, save the index and cmdline of the current command into the - // last_command_file. - // Upon resuming an update, read the saved index first; then - // 1. In verification mode, check if the 'move' or 'diff' commands before the saved index has - // the expected target blocks already. If not, these commands cannot be skipped and we need - // to attempt to execute them again. Therefore, we will delete the last_command_file so that - // the update will resume from the start of the transfer list. - // 2. In update mode, skip all commands before the saved index. Therefore, we can avoid deleting - // stashes with duplicate id unintentionally (b/69858743); and also speed up the update. - // If an update succeeds or is unresumable, delete the last_command_file. - bool skip_executed_command = true; - size_t saved_last_command_index; - if (!ParseLastCommandFile(&saved_last_command_index)) { - DeleteLastCommandFile(); - // We failed to parse the last command. Disallow skipping executed commands. - skip_executed_command = false; - } - - int rc = -1; - - // Subsequent lines are all individual transfer commands - for (size_t i = kTransferListHeaderLines; i < lines.size(); i++) { - const std::string& line = lines[i]; - if (line.empty()) continue; - - size_t cmdindex = i - kTransferListHeaderLines; - params.tokens = android::base::Split(line, " "); - params.cpos = 0; - params.cmdname = params.tokens[params.cpos++]; - params.cmdline = line; - params.target_verified = false; - - Command::Type cmd_type = Command::ParseType(params.cmdname); - if (cmd_type == Command::Type::LAST) { - LOG(ERROR) << "unexpected command [" << params.cmdname << "]"; - goto pbiudone; - } - - const CommandFunction& performer = command_map.at(cmd_type); - - // Skip the command if we explicitly set the corresponding function pointer to nullptr, e.g. - // "erase" during block_image_verify. - if (performer == nullptr) { - LOG(DEBUG) << "skip executing command [" << line << "]"; - continue; - } - - // Skip all commands before the saved last command index when resuming an update, except for - // "new" command. Because new commands read in the data sequentially. - if (params.canwrite && skip_executed_command && cmdindex <= saved_last_command_index && - cmd_type != Command::Type::NEW) { - LOG(INFO) << "Skipping already executed command: " << cmdindex - << ", last executed command for previous update: " << saved_last_command_index; - continue; - } - - if (performer(params) == -1) { - LOG(ERROR) << "failed to execute command [" << line << "]"; - if (cmd_type == Command::Type::COMPUTE_HASH_TREE && failure_type == kNoCause) { - failure_type = kHashTreeComputationFailure; - } - goto pbiudone; - } - - // In verify mode, check if the commands before the saved last_command_index have been executed - // correctly. If some target blocks have unexpected contents, delete the last command file so - // that we will resume the update from the first command in the transfer list. - if (!params.canwrite && skip_executed_command && cmdindex <= saved_last_command_index) { - // TODO(xunchang) check that the cmdline of the saved index is correct. - if ((cmd_type == Command::Type::MOVE || cmd_type == Command::Type::BSDIFF || - cmd_type == Command::Type::IMGDIFF) && - !params.target_verified) { - LOG(WARNING) << "Previously executed command " << saved_last_command_index << ": " - << params.cmdline << " doesn't produce expected target blocks."; - skip_executed_command = false; - DeleteLastCommandFile(); - } - } - - if (params.canwrite) { - if (fsync(params.fd) == -1) { - failure_type = errno == EIO ? kEioFailure : kFsyncFailure; - PLOG(ERROR) << "fsync failed"; - goto pbiudone; - } - - if (!UpdateLastCommandIndex(cmdindex, params.cmdline)) { - LOG(WARNING) << "Failed to update the last command file."; - } - - updater->WriteToCommandPipe( - android::base::StringPrintf("set_progress %.4f", - static_cast(params.written) / total_blocks), - true); - } - } - - rc = 0; - -pbiudone: - if (params.canwrite) { - pthread_mutex_lock(¶ms.nti.mu); - if (params.nti.receiver_available) { - LOG(WARNING) << "new data receiver is still available after executing all commands."; - } - params.nti.receiver_available = false; - pthread_cond_broadcast(¶ms.nti.cv); - pthread_mutex_unlock(¶ms.nti.mu); - int ret = pthread_join(params.thread, nullptr); - if (ret != 0) { - LOG(WARNING) << "pthread join returned with " << strerror(ret); - } - - if (rc == 0) { - LOG(INFO) << "wrote " << params.written << " blocks; expected " << total_blocks; - LOG(INFO) << "stashed " << params.stashed << " blocks"; - LOG(INFO) << "max alloc needed was " << params.buffer.size(); - - const char* partition = strrchr(block_device_path.c_str(), '/'); - if (partition != nullptr && *(partition + 1) != 0) { - updater->WriteToCommandPipe( - android::base::StringPrintf("log bytes_written_%s: %" PRIu64, partition + 1, - static_cast(params.written) * BLOCKSIZE)); - updater->WriteToCommandPipe( - android::base::StringPrintf("log bytes_stashed_%s: %" PRIu64, partition + 1, - static_cast(params.stashed) * BLOCKSIZE), - true); - } - // Delete stash only after successfully completing the update, as it may contain blocks needed - // to complete the update later. - DeleteStash(params.stashbase); - DeleteLastCommandFile(); - - // Create a marker on /cache partition, which allows skipping the update on this partition on - // retry. The marker will be removed once booting into normal boot, or before starting next - // fresh install. - if (!SetUpdatedMarker(updated_marker)) { - LOG(WARNING) << "Failed to set updated marker; continuing"; - } - } - - pthread_mutex_destroy(¶ms.nti.mu); - pthread_cond_destroy(¶ms.nti.cv); - } else if (rc == 0) { - LOG(INFO) << "verified partition contents; update may be resumed"; - } - - if (fsync(params.fd) == -1) { - failure_type = errno == EIO ? kEioFailure : kFsyncFailure; - PLOG(ERROR) << "fsync failed"; - } - // 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); - } - - // Delete the last command file if the update cannot be resumed. - if (params.isunresumable) { - DeleteLastCommandFile(); - } - - // 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)) { - DeleteStash(params.stashbase); - } - - if (failure_type != kNoCause && state->cause_code == kNoCause) { - state->cause_code = failure_type; - } - - return StringValue(rc == 0 ? "t" : ""); -} - -/** - * The transfer list is a text file containing commands to transfer data from one place to another - * on the target partition. We parse it and execute the commands in order: - * - * zero [rangeset] - * - Fill the indicated blocks with zeros. - * - * new [rangeset] - * - Fill the blocks with data read from the new_data file. - * - * erase [rangeset] - * - Mark the given blocks as empty. - * - * move <...> - * bsdiff <...> - * imgdiff <...> - * - Read the source blocks, apply a patch (or not in the case of move), write result to target - * blocks. bsdiff or imgdiff specifies the type of patch; move means no patch at all. - * - * See the comments in LoadSrcTgtVersion3() for a description of the <...> format. - * - * stash - * - Load the given source range and stash the data in the given slot of the stash table. - * - * free - * - Free the given stash data. - * - * The creator of the transfer list will guarantee that no block is read (ie, used as the source for - * a patch or move) after it has been written. - * - * The creator will guarantee that a given stash is loaded (with a stash command) before it's used - * in a move/bsdiff/imgdiff command. - * - * Within one command the source and target ranges may overlap so in general we need to read the - * entire source into memory before writing anything to the target blocks. - * - * All the patch data is concatenated into one patch_data file in the update package. It must be - * stored uncompressed because we memory-map it in directly from the archive. (Since patches are - * already compressed, we lose very little by not compressing their concatenation.) - * - * Commands that read data from the partition (i.e. move/bsdiff/imgdiff/stash) have one or more - * additional hashes before the range parameters, which are used to check if the command has already - * been completed and verify the integrity of the source data. - */ -Value* BlockImageVerifyFn(const char* name, State* state, - const std::vector>& argv) { - // Commands which are not allowed are set to nullptr to skip them completely. - const CommandMap command_map{ - // clang-format off - { Command::Type::ABORT, PerformCommandAbort }, - { Command::Type::BSDIFF, PerformCommandDiff }, - { Command::Type::COMPUTE_HASH_TREE, nullptr }, - { Command::Type::ERASE, nullptr }, - { Command::Type::FREE, PerformCommandFree }, - { Command::Type::IMGDIFF, PerformCommandDiff }, - { Command::Type::MOVE, PerformCommandMove }, - { Command::Type::NEW, nullptr }, - { Command::Type::STASH, PerformCommandStash }, - { Command::Type::ZERO, nullptr }, - // clang-format on - }; - CHECK_EQ(static_cast(Command::Type::LAST), command_map.size()); - - // Perform a dry run without writing to test if an update can proceed. - return PerformBlockImageUpdate(name, state, argv, command_map, true); -} - -Value* BlockImageUpdateFn(const char* name, State* state, - const std::vector>& argv) { - const CommandMap command_map{ - // clang-format off - { Command::Type::ABORT, PerformCommandAbort }, - { Command::Type::BSDIFF, PerformCommandDiff }, - { Command::Type::COMPUTE_HASH_TREE, PerformCommandComputeHashTree }, - { Command::Type::ERASE, PerformCommandErase }, - { Command::Type::FREE, PerformCommandFree }, - { Command::Type::IMGDIFF, PerformCommandDiff }, - { Command::Type::MOVE, PerformCommandMove }, - { Command::Type::NEW, PerformCommandNew }, - { Command::Type::STASH, PerformCommandStash }, - { Command::Type::ZERO, PerformCommandZero }, - // clang-format on - }; - CHECK_EQ(static_cast(Command::Type::LAST), command_map.size()); - - return PerformBlockImageUpdate(name, state, argv, command_map, false); -} - -Value* RangeSha1Fn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 2) { - ErrorAbort(state, kArgsParsingFailure, "range_sha1 expects 2 arguments, got %zu", argv.size()); - return StringValue(""); - } - - std::vector> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } - - const std::unique_ptr& blockdev_filename = args[0]; - const std::unique_ptr& ranges = args[1]; - - if (blockdev_filename->type != Value::Type::STRING) { - ErrorAbort(state, kArgsParsingFailure, "blockdev_filename argument to %s must be string", name); - return StringValue(""); - } - if (ranges->type != Value::Type::STRING) { - ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); - return StringValue(""); - } - - auto block_device_path = state->updater->FindBlockDeviceName(blockdev_filename->data); - if (block_device_path.empty()) { - LOG(ERROR) << "Block device path for " << blockdev_filename->data << " not found. " << name - << " failed."; - return StringValue(""); - } - - android::base::unique_fd fd(open(block_device_path.c_str(), O_RDWR)); - if (fd == -1) { - CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure; - ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(), - strerror(errno)); - return StringValue(""); - } - - RangeSet rs = RangeSet::Parse(ranges->data); - CHECK(static_cast(rs)); - - SHA_CTX ctx; - SHA1_Init(&ctx); - - std::vector buffer(BLOCKSIZE); - for (const auto& [begin, end] : rs) { - if (!check_lseek(fd, static_cast(begin) * BLOCKSIZE, SEEK_SET)) { - ErrorAbort(state, kLseekFailure, "failed to seek %s: %s", block_device_path.c_str(), - strerror(errno)); - return StringValue(""); - } - - for (size_t j = begin; j < end; ++j) { - if (!android::base::ReadFully(fd, buffer.data(), BLOCKSIZE)) { - CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure; - ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(), - strerror(errno)); - return StringValue(""); - } - - SHA1_Update(&ctx, buffer.data(), BLOCKSIZE); - } - } - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1_Final(digest, &ctx); - - return StringValue(print_sha1(digest)); -} - -// This function checks if a device has been remounted R/W prior to an incremental -// OTA update. This is an common cause of update abortion. The function reads the -// 1st block of each partition and check for mounting time/count. It return string "t" -// if executes successfully and an empty string otherwise. - -Value* CheckFirstBlockFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 1) { - ErrorAbort(state, kArgsParsingFailure, "check_first_block expects 1 argument, got %zu", - argv.size()); - return StringValue(""); - } - - std::vector> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } - - const std::unique_ptr& arg_filename = args[0]; - - if (arg_filename->type != Value::Type::STRING) { - ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); - return StringValue(""); - } - - auto block_device_path = state->updater->FindBlockDeviceName(arg_filename->data); - if (block_device_path.empty()) { - LOG(ERROR) << "Block device path for " << arg_filename->data << " not found. " << name - << " failed."; - return StringValue(""); - } - - android::base::unique_fd fd(open(block_device_path.c_str(), O_RDONLY)); - if (fd == -1) { - CauseCode cause_code = errno == EIO ? kEioFailure : kFileOpenFailure; - ErrorAbort(state, cause_code, "open \"%s\" failed: %s", block_device_path.c_str(), - strerror(errno)); - return StringValue(""); - } - - RangeSet blk0(std::vector{ Range{ 0, 1 } }); - std::vector block0_buffer(BLOCKSIZE); - - if (ReadBlocks(blk0, &block0_buffer, fd) == -1) { - CauseCode cause_code = errno == EIO ? kEioFailure : kFreadFailure; - ErrorAbort(state, cause_code, "failed to read %s: %s", block_device_path.c_str(), - strerror(errno)); - return StringValue(""); - } - - // https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout - // Super block starts from block 0, offset 0x400 - // 0x2C: len32 Mount time - // 0x30: len32 Write time - // 0x34: len16 Number of mounts since the last fsck - // 0x38: len16 Magic signature 0xEF53 - - time_t mount_time = *reinterpret_cast(&block0_buffer[0x400 + 0x2C]); - uint16_t mount_count = *reinterpret_cast(&block0_buffer[0x400 + 0x34]); - - if (mount_count > 0) { - state->updater->UiPrint( - android::base::StringPrintf("Device was remounted R/W %" PRIu16 " times", mount_count)); - state->updater->UiPrint( - android::base::StringPrintf("Last remount happened on %s", ctime(&mount_time))); - } - - return StringValue("t"); -} - -Value* BlockImageRecoverFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 2) { - ErrorAbort(state, kArgsParsingFailure, "block_image_recover expects 2 arguments, got %zu", - argv.size()); - return StringValue(""); - } - - std::vector> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } - - const std::unique_ptr& filename = args[0]; - const std::unique_ptr& ranges = args[1]; - - if (filename->type != Value::Type::STRING) { - ErrorAbort(state, kArgsParsingFailure, "filename argument to %s must be string", name); - return StringValue(""); - } - if (ranges->type != Value::Type::STRING) { - ErrorAbort(state, kArgsParsingFailure, "ranges argument to %s must be string", name); - return StringValue(""); - } - RangeSet rs = RangeSet::Parse(ranges->data); - if (!rs) { - ErrorAbort(state, kArgsParsingFailure, "failed to parse ranges: %s", ranges->data.c_str()); - return StringValue(""); - } - - auto block_device_path = state->updater->FindBlockDeviceName(filename->data); - if (block_device_path.empty()) { - LOG(ERROR) << "Block device path for " << filename->data << " not found. " << name - << " failed."; - return StringValue(""); - } - - // Output notice to log when recover is attempted - LOG(INFO) << block_device_path << " image corrupted, attempting to recover..."; - - // When opened with O_RDWR, libfec rewrites corrupted blocks when they are read - fec::io fh(block_device_path, O_RDWR); - - if (!fh) { - ErrorAbort(state, kLibfecFailure, "fec_open \"%s\" failed: %s", block_device_path.c_str(), - strerror(errno)); - return StringValue(""); - } - - if (!fh.has_ecc() || !fh.has_verity()) { - ErrorAbort(state, kLibfecFailure, "unable to use metadata to correct errors"); - return StringValue(""); - } - - fec_status status; - if (!fh.get_status(status)) { - ErrorAbort(state, kLibfecFailure, "failed to read FEC status"); - return StringValue(""); - } - - uint8_t buffer[BLOCKSIZE]; - for (const auto& [begin, end] : rs) { - for (size_t j = begin; j < end; ++j) { - // Stay within the data area, libfec validates and corrects metadata - if (status.data_size <= static_cast(j) * BLOCKSIZE) { - continue; - } - - if (fh.pread(buffer, BLOCKSIZE, static_cast(j) * BLOCKSIZE) != BLOCKSIZE) { - ErrorAbort(state, kLibfecFailure, "failed to recover %s (block %zu): %s", - block_device_path.c_str(), j, strerror(errno)); - return StringValue(""); - } - - // If we want to be able to recover from a situation where rewriting a corrected - // block doesn't guarantee the same data will be returned when re-read later, we - // can save a copy of corrected blocks to /cache. Note: - // - // 1. Maximum space required from /cache is the same as the maximum number of - // corrupted blocks we can correct. For RS(255, 253) and a 2 GiB partition, - // this would be ~16 MiB, for example. - // - // 2. To find out if this block was corrupted, call fec_get_status after each - // read and check if the errors field value has increased. - } - } - LOG(INFO) << "..." << block_device_path << " image recovered successfully."; - return StringValue("t"); -} - -void RegisterBlockImageFunctions() { - RegisterFunction("block_image_verify", BlockImageVerifyFn); - RegisterFunction("block_image_update", BlockImageUpdateFn); - RegisterFunction("block_image_recover", BlockImageRecoverFn); - RegisterFunction("check_first_block", CheckFirstBlockFn); - RegisterFunction("range_sha1", RangeSha1Fn); -} diff --git a/updater/build_info.cpp b/updater/build_info.cpp deleted file mode 100644 index f168008e..00000000 --- a/updater/build_info.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "updater/build_info.h" - -#include - -#include -#include - -#include -#include -#include - -#include "updater/target_files.h" - -bool BuildInfo::ParseTargetFile(const std::string_view target_file_path, bool extracted_input) { - TargetFile target_file(std::string(target_file_path), extracted_input); - if (!target_file.Open()) { - return false; - } - - if (!target_file.GetBuildProps(&build_props_)) { - return false; - } - - std::vector fstab_info_list; - if (!target_file.ParseFstabInfo(&fstab_info_list)) { - return false; - } - - for (const auto& fstab_info : fstab_info_list) { - for (const auto& directory : { "IMAGES", "RADIO" }) { - std::string entry_name = directory + fstab_info.mount_point + ".img"; - if (!target_file.EntryExists(entry_name)) { - LOG(WARNING) << "Failed to find the image entry in the target file: " << entry_name; - continue; - } - - temp_files_.emplace_back(work_dir_); - auto& image_file = temp_files_.back(); - if (!target_file.ExtractImage(entry_name, fstab_info, work_dir_, &image_file)) { - LOG(ERROR) << "Failed to set up source image files."; - return false; - } - - std::string mapped_path = image_file.path; - // Rename the images to more readable ones if we want to keep the image. - if (keep_images_) { - mapped_path = work_dir_ + fstab_info.mount_point + ".img"; - image_file.release(); - if (rename(image_file.path, mapped_path.c_str()) != 0) { - PLOG(ERROR) << "Failed to rename " << image_file.path << " to " << mapped_path; - return false; - } - } - - LOG(INFO) << "Mounted " << fstab_info.mount_point << "\nMapping: " << fstab_info.blockdev_name - << " to " << mapped_path; - - blockdev_map_.emplace( - fstab_info.blockdev_name, - FakeBlockDevice(fstab_info.blockdev_name, fstab_info.mount_point, mapped_path)); - break; - } - } - - return true; -} - -std::string BuildInfo::GetProperty(const std::string_view key, - const std::string_view default_value) const { - // The logic to parse the ro.product properties should be in line with the generation script. - // More details in common.py BuildInfo.GetBuildProp. - // TODO(xunchang) handle the oem property and the source order defined in - // ro.product.property_source_order - const std::set> ro_product_props = { - "ro.product.brand", "ro.product.device", "ro.product.manufacturer", "ro.product.model", - "ro.product.name" - }; - const std::vector source_order = { - "product", "odm", "vendor", "system_ext", "system", - }; - if (ro_product_props.find(key) != ro_product_props.end()) { - std::string_view key_suffix(key); - CHECK(android::base::ConsumePrefix(&key_suffix, "ro.product")); - for (const auto& source : source_order) { - std::string resolved_key = "ro.product." + source + std::string(key_suffix); - if (auto entry = build_props_.find(resolved_key); entry != build_props_.end()) { - return entry->second; - } - } - LOG(WARNING) << "Failed to find property: " << key; - return std::string(default_value); - } else if (key == "ro.build.fingerprint") { - // clang-format off - return android::base::StringPrintf("%s/%s/%s:%s/%s/%s:%s/%s", - GetProperty("ro.product.brand", "").c_str(), - GetProperty("ro.product.name", "").c_str(), - GetProperty("ro.product.device", "").c_str(), - GetProperty("ro.build.version.release", "").c_str(), - GetProperty("ro.build.id", "").c_str(), - GetProperty("ro.build.version.incremental", "").c_str(), - GetProperty("ro.build.type", "").c_str(), - GetProperty("ro.build.tags", "").c_str()); - // clang-format on - } - - auto entry = build_props_.find(key); - if (entry == build_props_.end()) { - LOG(WARNING) << "Failed to find property: " << key; - return std::string(default_value); - } - - return entry->second; -} - -std::string BuildInfo::FindBlockDeviceName(const std::string_view name) const { - auto entry = blockdev_map_.find(name); - if (entry == blockdev_map_.end()) { - LOG(WARNING) << "Failed to find path to block device " << name; - return ""; - } - - return entry->second.mounted_file_path; -} diff --git a/updater/commands.cpp b/updater/commands.cpp deleted file mode 100644 index 1a7c272b..00000000 --- a/updater/commands.cpp +++ /dev/null @@ -1,453 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "private/commands.h" - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "otautil/print_sha1.h" -#include "otautil/rangeset.h" - -using namespace std::string_literals; - -bool Command::abort_allowed_ = false; - -Command::Command(Type type, size_t index, std::string cmdline, HashTreeInfo hash_tree_info) - : type_(type), - index_(index), - cmdline_(std::move(cmdline)), - hash_tree_info_(std::move(hash_tree_info)) { - CHECK(type == Type::COMPUTE_HASH_TREE); -} - -Command::Type Command::ParseType(const std::string& type_str) { - if (type_str == "abort") { - if (!abort_allowed_) { - LOG(ERROR) << "ABORT disallowed"; - return Type::LAST; - } - return Type::ABORT; - } else if (type_str == "bsdiff") { - return Type::BSDIFF; - } else if (type_str == "compute_hash_tree") { - return Type::COMPUTE_HASH_TREE; - } else if (type_str == "erase") { - return Type::ERASE; - } else if (type_str == "free") { - return Type::FREE; - } else if (type_str == "imgdiff") { - return Type::IMGDIFF; - } else if (type_str == "move") { - return Type::MOVE; - } else if (type_str == "new") { - return Type::NEW; - } else if (type_str == "stash") { - return Type::STASH; - } else if (type_str == "zero") { - return Type::ZERO; - } - return Type::LAST; -}; - -bool Command::ParseTargetInfoAndSourceInfo(const std::vector& tokens, - const std::string& tgt_hash, TargetInfo* target, - const std::string& src_hash, SourceInfo* source, - std::string* err) { - // We expect the given args (in 'tokens' vector) in one of the following formats. - // - // - <[stash_id:location] ...> - // (loads data from stashes only) - // - // - // (loads data from source image only) - // - // <[stash_id:location] ...> - // (loads data from both of source image and stashes) - - // At least it needs to provide three args: , and "-"/. - if (tokens.size() < 3) { - *err = "invalid number of args"; - return false; - } - - size_t pos = 0; - RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]); - if (!tgt_ranges) { - *err = "invalid target ranges"; - return false; - } - *target = TargetInfo(tgt_hash, tgt_ranges); - - // - const std::string& token = tokens[pos++]; - size_t src_blocks; - if (!android::base::ParseUint(token, &src_blocks)) { - *err = "invalid src_block_count \""s + token + "\""; - return false; - } - - RangeSet src_ranges; - RangeSet src_ranges_location; - // "-" or [] - if (tokens[pos] == "-") { - // no source ranges, only stashes - pos++; - } else { - src_ranges = RangeSet::Parse(tokens[pos++]); - if (!src_ranges) { - *err = "invalid source ranges"; - return false; - } - - if (pos >= tokens.size()) { - // No stashes, only source ranges. - SourceInfo result(src_hash, src_ranges, {}, {}); - - if (result.blocks() != src_blocks) { - *err = - android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(), - src_ranges.ToString().c_str(), src_blocks); - return false; - } - - *source = result; - return true; - } - - src_ranges_location = RangeSet::Parse(tokens[pos++]); - if (!src_ranges_location) { - *err = "invalid source ranges location"; - return false; - } - } - - // <[stash_id:stash_location]> - std::vector stashes; - while (pos < tokens.size()) { - // Each word is a an index into the stash table, a colon, and then a RangeSet describing where - // in the source block that stashed data should go. - std::vector pairs = android::base::Split(tokens[pos++], ":"); - if (pairs.size() != 2) { - *err = "invalid stash info"; - return false; - } - RangeSet stash_location = RangeSet::Parse(pairs[1]); - if (!stash_location) { - *err = "invalid stash location"; - return false; - } - stashes.emplace_back(pairs[0], stash_location); - } - - SourceInfo result(src_hash, src_ranges, src_ranges_location, stashes); - if (src_blocks != result.blocks()) { - *err = android::base::StringPrintf("mismatching block count: %zu (%s) vs %zu", result.blocks(), - src_ranges.ToString().c_str(), src_blocks); - return false; - } - - *source = result; - return true; -} - -Command Command::Parse(const std::string& line, size_t index, std::string* err) { - std::vector tokens = android::base::Split(line, " "); - size_t pos = 0; - // tokens.size() will be 1 at least. - Type op = ParseType(tokens[pos++]); - if (op == Type::LAST) { - *err = "invalid type"; - return {}; - } - - PatchInfo patch_info; - TargetInfo target_info; - SourceInfo source_info; - StashInfo stash_info; - - if (op == Type::ZERO || op == Type::NEW || op == Type::ERASE) { - // zero/new/erase - if (pos + 1 != tokens.size()) { - *err = android::base::StringPrintf("invalid number of args: %zu (expected 1)", - tokens.size() - pos); - return {}; - } - RangeSet tgt_ranges = RangeSet::Parse(tokens[pos++]); - if (!tgt_ranges) { - return {}; - } - static const std::string kUnknownHash{ "unknown-hash" }; - target_info = TargetInfo(kUnknownHash, tgt_ranges); - } else if (op == Type::STASH) { - // stash - if (pos + 2 != tokens.size()) { - *err = android::base::StringPrintf("invalid number of args: %zu (expected 2)", - tokens.size() - pos); - return {}; - } - const std::string& id = tokens[pos++]; - RangeSet src_ranges = RangeSet::Parse(tokens[pos++]); - if (!src_ranges) { - *err = "invalid token"; - return {}; - } - stash_info = StashInfo(id, src_ranges); - } else if (op == Type::FREE) { - // free - if (pos + 1 != tokens.size()) { - *err = android::base::StringPrintf("invalid number of args: %zu (expected 1)", - tokens.size() - pos); - return {}; - } - stash_info = StashInfo(tokens[pos++], {}); - } else if (op == Type::MOVE) { - // - if (pos + 1 > tokens.size()) { - *err = "missing hash"; - return {}; - } - std::string hash = tokens[pos++]; - if (!ParseTargetInfoAndSourceInfo( - std::vector(tokens.cbegin() + pos, tokens.cend()), hash, &target_info, - hash, &source_info, err)) { - return {}; - } - } else if (op == Type::BSDIFF || op == Type::IMGDIFF) { - // - if (pos + 4 > tokens.size()) { - *err = android::base::StringPrintf("invalid number of args: %zu (expected 4+)", - tokens.size() - pos); - return {}; - } - size_t offset; - size_t length; - if (!android::base::ParseUint(tokens[pos++], &offset) || - !android::base::ParseUint(tokens[pos++], &length)) { - *err = "invalid patch offset/length"; - return {}; - } - patch_info = PatchInfo(offset, length); - - std::string src_hash = tokens[pos++]; - std::string dst_hash = tokens[pos++]; - if (!ParseTargetInfoAndSourceInfo( - std::vector(tokens.cbegin() + pos, tokens.cend()), dst_hash, &target_info, - src_hash, &source_info, err)) { - return {}; - } - } else if (op == Type::ABORT) { - // Abort takes no arguments, so there's nothing else to check. - if (pos != tokens.size()) { - *err = android::base::StringPrintf("invalid number of args: %zu (expected 0)", - tokens.size() - pos); - return {}; - } - } else if (op == Type::COMPUTE_HASH_TREE) { - // - if (pos + 5 != tokens.size()) { - *err = android::base::StringPrintf("invalid number of args: %zu (expected 5)", - tokens.size() - pos); - return {}; - } - - // Expects the hash_tree data to be contiguous. - RangeSet hash_tree_ranges = RangeSet::Parse(tokens[pos++]); - if (!hash_tree_ranges || hash_tree_ranges.size() != 1) { - *err = "invalid hash tree ranges in: " + line; - return {}; - } - - RangeSet source_ranges = RangeSet::Parse(tokens[pos++]); - if (!source_ranges) { - *err = "invalid source ranges in: " + line; - return {}; - } - - std::string hash_algorithm = tokens[pos++]; - std::string salt_hex = tokens[pos++]; - std::string root_hash = tokens[pos++]; - if (hash_algorithm.empty() || salt_hex.empty() || root_hash.empty()) { - *err = "invalid hash tree arguments in " + line; - return {}; - } - - HashTreeInfo hash_tree_info(std::move(hash_tree_ranges), std::move(source_ranges), - std::move(hash_algorithm), std::move(salt_hex), - std::move(root_hash)); - return Command(op, index, line, std::move(hash_tree_info)); - } else { - *err = "invalid op"; - return {}; - } - - return Command(op, index, line, patch_info, target_info, source_info, stash_info); -} - -bool SourceInfo::Overlaps(const TargetInfo& target) const { - return ranges_.Overlaps(target.ranges()); -} - -// Moves blocks in the 'source' vector to the specified locations (as in 'locs') in the 'dest' -// vector. Note that source and dest may be the same buffer. -static void MoveRange(std::vector* dest, const RangeSet& locs, - const std::vector& source, size_t block_size) { - const uint8_t* from = source.data(); - uint8_t* to = dest->data(); - size_t start = locs.blocks(); - // Must do the movement backward. - for (auto it = locs.crbegin(); it != locs.crend(); it++) { - size_t blocks = it->second - it->first; - start -= blocks; - memmove(to + (it->first * block_size), from + (start * block_size), blocks * block_size); - } -} - -bool SourceInfo::ReadAll( - std::vector* buffer, size_t block_size, - const std::function*)>& block_reader, - const std::function*)>& stash_reader) const { - if (buffer->size() < blocks() * block_size) { - return false; - } - - // Read in the source ranges. - if (ranges_) { - if (block_reader(ranges_, buffer) != 0) { - return false; - } - if (location_) { - MoveRange(buffer, location_, *buffer, block_size); - } - } - - // Read in the stashes. - for (const StashInfo& stash : stashes_) { - std::vector stash_buffer(stash.blocks() * block_size); - if (stash_reader(stash.id(), &stash_buffer) != 0) { - return false; - } - MoveRange(buffer, stash.ranges(), stash_buffer, block_size); - } - return true; -} - -void SourceInfo::DumpBuffer(const std::vector& buffer, size_t block_size) const { - LOG(INFO) << "Dumping hashes in hex for " << ranges_.blocks() << " source blocks"; - - const RangeSet& location = location_ ? location_ : RangeSet({ Range{ 0, ranges_.blocks() } }); - for (size_t i = 0; i < ranges_.blocks(); i++) { - size_t block_num = ranges_.GetBlockNumber(i); - size_t buffer_index = location.GetBlockNumber(i); - CHECK_LE((buffer_index + 1) * block_size, buffer.size()); - - uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(buffer.data() + buffer_index * block_size, block_size, digest); - std::string hexdigest = print_sha1(digest); - LOG(INFO) << " block number: " << block_num << ", SHA-1: " << hexdigest; - } -} - -std::ostream& operator<<(std::ostream& os, const Command& command) { - os << command.index() << ": " << command.cmdline(); - return os; -} - -std::ostream& operator<<(std::ostream& os, const TargetInfo& target) { - os << target.blocks() << " blocks (" << target.hash_ << "): " << target.ranges_.ToString(); - return os; -} - -std::ostream& operator<<(std::ostream& os, const StashInfo& stash) { - os << stash.blocks() << " blocks (" << stash.id_ << "): " << stash.ranges_.ToString(); - return os; -} - -std::ostream& operator<<(std::ostream& os, const SourceInfo& source) { - os << source.blocks_ << " blocks (" << source.hash_ << "): "; - if (source.ranges_) { - os << source.ranges_.ToString(); - if (source.location_) { - os << " (location: " << source.location_.ToString() << ")"; - } - } - if (!source.stashes_.empty()) { - os << " " << source.stashes_.size() << " stash(es)"; - } - return os; -} - -TransferList TransferList::Parse(const std::string& transfer_list_str, std::string* err) { - TransferList result{}; - - std::vector lines = android::base::Split(transfer_list_str, "\n"); - if (lines.size() < kTransferListHeaderLines) { - *err = android::base::StringPrintf("too few lines in the transfer list [%zu]", lines.size()); - return TransferList{}; - } - - // First line in transfer list is the version number. - if (!android::base::ParseInt(lines[0], &result.version_, 3, 4)) { - *err = "unexpected transfer list version ["s + lines[0] + "]"; - return TransferList{}; - } - - // Second line in transfer list is the total number of blocks we expect to write. - if (!android::base::ParseUint(lines[1], &result.total_blocks_)) { - *err = "unexpected block count ["s + lines[1] + "]"; - return TransferList{}; - } - - // Third line is how many stash entries are needed simultaneously. - if (!android::base::ParseUint(lines[2], &result.stash_max_entries_)) { - return TransferList{}; - } - - // Fourth line is the maximum number of blocks that will be stashed simultaneously. - if (!android::base::ParseUint(lines[3], &result.stash_max_blocks_)) { - *err = "unexpected maximum stash blocks ["s + lines[3] + "]"; - return TransferList{}; - } - - // Subsequent lines are all individual transfer commands. - for (size_t i = kTransferListHeaderLines; i < lines.size(); i++) { - const std::string& line = lines[i]; - if (line.empty()) continue; - - size_t cmdindex = i - kTransferListHeaderLines; - std::string parsing_error; - Command command = Command::Parse(line, cmdindex, &parsing_error); - if (!command) { - *err = android::base::StringPrintf("Failed to parse command %zu [%s]: %s", cmdindex, - line.c_str(), parsing_error.c_str()); - return TransferList{}; - } - result.commands_.push_back(command); - } - - return result; -} diff --git a/updater/dynamic_partitions.cpp b/updater/dynamic_partitions.cpp deleted file mode 100644 index a340116f..00000000 --- a/updater/dynamic_partitions.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "updater/dynamic_partitions.h" - -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include "edify/expr.h" -#include "edify/updater_runtime_interface.h" -#include "otautil/error_code.h" -#include "otautil/paths.h" -#include "private/utils.h" - -static std::vector ReadStringArgs(const char* name, State* state, - const std::vector>& argv, - const std::vector& arg_names) { - if (argv.size() != arg_names.size()) { - ErrorAbort(state, kArgsParsingFailure, "%s expects %zu arguments, got %zu", name, - arg_names.size(), argv.size()); - return {}; - } - - std::vector> args; - if (!ReadValueArgs(state, argv, &args)) { - return {}; - } - - CHECK_EQ(args.size(), arg_names.size()); - - for (size_t i = 0; i < arg_names.size(); ++i) { - if (args[i]->type != Value::Type::STRING) { - ErrorAbort(state, kArgsParsingFailure, "%s argument to %s must be string", - arg_names[i].c_str(), name); - return {}; - } - } - - std::vector ret; - std::transform(args.begin(), args.end(), std::back_inserter(ret), - [](const auto& arg) { return arg->data; }); - return ret; -} - -Value* UnmapPartitionFn(const char* name, State* state, - const std::vector>& argv) { - auto args = ReadStringArgs(name, state, argv, { "name" }); - if (args.empty()) return StringValue(""); - - auto updater_runtime = state->updater->GetRuntime(); - return updater_runtime->UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t") - : StringValue(""); -} - -Value* MapPartitionFn(const char* name, State* state, - const std::vector>& argv) { - auto args = ReadStringArgs(name, state, argv, { "name" }); - if (args.empty()) return StringValue(""); - - std::string path; - auto updater_runtime = state->updater->GetRuntime(); - bool result = updater_runtime->MapPartitionOnDeviceMapper(args[0], &path); - return result ? StringValue(path) : StringValue(""); -} - -static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED"; - -Value* UpdateDynamicPartitionsFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 1) { - ErrorAbort(state, kArgsParsingFailure, "%s expects 1 arguments, got %zu", name, argv.size()); - return StringValue(""); - } - std::vector> args; - if (!ReadValueArgs(state, argv, &args)) { - return nullptr; - } - const std::unique_ptr& op_list_value = args[0]; - if (op_list_value->type != Value::Type::BLOB) { - ErrorAbort(state, kArgsParsingFailure, "op_list argument to %s must be blob", name); - return StringValue(""); - } - - std::string updated_marker = Paths::Get().stash_directory_base() + kMetadataUpdatedMarker; - if (state->is_retry) { - struct stat sb; - int result = stat(updated_marker.c_str(), &sb); - if (result == 0) { - LOG(INFO) << "Skipping already updated dynamic partition metadata based on marker"; - return StringValue("t"); - } - } else { - // Delete the obsolete marker if any. - std::string err; - if (!android::base::RemoveFileIfExists(updated_marker, &err)) { - LOG(ERROR) << "Failed to remove dynamic partition metadata updated marker " << updated_marker - << ": " << err; - return StringValue(""); - } - } - - auto updater_runtime = state->updater->GetRuntime(); - if (!updater_runtime->UpdateDynamicPartitions(op_list_value->data)) { - return StringValue(""); - } - - if (!SetUpdatedMarker(updated_marker)) { - LOG(ERROR) << "Failed to set metadata updated marker."; - return StringValue(""); - } - - return StringValue("t"); -} - -void RegisterDynamicPartitionsFunctions() { - RegisterFunction("unmap_partition", UnmapPartitionFn); - RegisterFunction("map_partition", MapPartitionFn); - RegisterFunction("update_dynamic_partitions", UpdateDynamicPartitionsFn); -} diff --git a/updater/include/private/commands.h b/updater/include/private/commands.h deleted file mode 100644 index 7a23bb78..00000000 --- a/updater/include/private/commands.h +++ /dev/null @@ -1,475 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -#include -#include -#include -#include - -#include // FRIEND_TEST - -#include "otautil/rangeset.h" - -// Represents the target info used in a Command. TargetInfo contains the ranges of the blocks and -// the expected hash. -class TargetInfo { - public: - TargetInfo() = default; - - TargetInfo(std::string hash, RangeSet ranges) - : hash_(std::move(hash)), ranges_(std::move(ranges)) {} - - const std::string& hash() const { - return hash_; - } - - const RangeSet& ranges() const { - return ranges_; - } - - size_t blocks() const { - return ranges_.blocks(); - } - - bool operator==(const TargetInfo& other) const { - return hash_ == other.hash_ && ranges_ == other.ranges_; - } - - private: - friend std::ostream& operator<<(std::ostream& os, const TargetInfo& source); - - // The hash of the data represented by the object. - std::string hash_; - // The block ranges that the data should be written to. - RangeSet ranges_; -}; - -std::ostream& operator<<(std::ostream& os, const TargetInfo& source); - -// Represents the stash info used in a Command. -class StashInfo { - public: - StashInfo() = default; - - StashInfo(std::string id, RangeSet ranges) : id_(std::move(id)), ranges_(std::move(ranges)) {} - - size_t blocks() const { - return ranges_.blocks(); - } - - const std::string& id() const { - return id_; - } - - const RangeSet& ranges() const { - return ranges_; - } - - bool operator==(const StashInfo& other) const { - return id_ == other.id_ && ranges_ == other.ranges_; - } - - private: - friend std::ostream& operator<<(std::ostream& os, const StashInfo& stash); - - // The id (i.e. hash) of the stash. - std::string id_; - // The matching location of the stash. - RangeSet ranges_; -}; - -std::ostream& operator<<(std::ostream& os, const StashInfo& stash); - -// Represents the source info in a Command, whose data could come from source image, stashed blocks, -// or both. -class SourceInfo { - public: - SourceInfo() = default; - - SourceInfo(std::string hash, RangeSet ranges, RangeSet location, std::vector stashes) - : hash_(std::move(hash)), - ranges_(std::move(ranges)), - location_(std::move(location)), - stashes_(std::move(stashes)) { - blocks_ = ranges_.blocks(); - for (const auto& stash : stashes_) { - blocks_ += stash.ranges().blocks(); - } - } - - // Reads all the data specified by this SourceInfo object into the given 'buffer', by calling the - // given readers. Caller needs to specify the block size for the represented blocks. The given - // buffer needs to be sufficiently large. Otherwise it returns false. 'block_reader' and - // 'stash_reader' read the specified data into the given buffer (guaranteed to be large enough) - // respectively. The readers should return 0 on success, or -1 on error. - bool ReadAll( - std::vector* buffer, size_t block_size, - const std::function*)>& block_reader, - const std::function*)>& stash_reader) const; - - // Whether this SourceInfo overlaps with the given TargetInfo object. - bool Overlaps(const TargetInfo& target) const; - - // Dumps the hashes in hex for the given buffer that's loaded from this SourceInfo object - // (excluding the stashed blocks which are handled separately). - void DumpBuffer(const std::vector& buffer, size_t block_size) const; - - const std::string& hash() const { - return hash_; - } - - size_t blocks() const { - return blocks_; - } - - bool operator==(const SourceInfo& other) const { - return hash_ == other.hash_ && ranges_ == other.ranges_ && location_ == other.location_ && - stashes_ == other.stashes_; - } - - private: - friend std::ostream& operator<<(std::ostream& os, const SourceInfo& source); - - // The hash of the data represented by the object. - std::string hash_; - // The block ranges from the source image to read data from. This could be a subset of all the - // blocks represented by the object, or empty if all the data should be loaded from stash. - RangeSet ranges_; - // The location in the buffer to load ranges_ into. Empty if ranges_ alone covers all the blocks - // (i.e. nothing needs to be loaded from stash). - RangeSet location_; - // The info for the stashed blocks that are part of the source. Empty if there's none. - std::vector stashes_; - // Total number of blocks represented by the object. - size_t blocks_{ 0 }; -}; - -std::ostream& operator<<(std::ostream& os, const SourceInfo& source); - -class PatchInfo { - public: - PatchInfo() = default; - - PatchInfo(size_t offset, size_t length) : offset_(offset), length_(length) {} - - size_t offset() const { - return offset_; - } - - size_t length() const { - return length_; - } - - bool operator==(const PatchInfo& other) const { - return offset_ == other.offset_ && length_ == other.length_; - } - - private: - size_t offset_{ 0 }; - size_t length_{ 0 }; -}; - -// The arguments to build a hash tree from blocks on the block device. -class HashTreeInfo { - public: - HashTreeInfo() = default; - - HashTreeInfo(RangeSet hash_tree_ranges, RangeSet source_ranges, std::string hash_algorithm, - std::string salt_hex, std::string root_hash) - : hash_tree_ranges_(std::move(hash_tree_ranges)), - source_ranges_(std::move(source_ranges)), - hash_algorithm_(std::move(hash_algorithm)), - salt_hex_(std::move(salt_hex)), - root_hash_(std::move(root_hash)) {} - - const RangeSet& hash_tree_ranges() const { - return hash_tree_ranges_; - } - const RangeSet& source_ranges() const { - return source_ranges_; - } - - const std::string& hash_algorithm() const { - return hash_algorithm_; - } - const std::string& salt_hex() const { - return salt_hex_; - } - const std::string& root_hash() const { - return root_hash_; - } - - bool operator==(const HashTreeInfo& other) const { - return hash_tree_ranges_ == other.hash_tree_ranges_ && source_ranges_ == other.source_ranges_ && - hash_algorithm_ == other.hash_algorithm_ && salt_hex_ == other.salt_hex_ && - root_hash_ == other.root_hash_; - } - - private: - RangeSet hash_tree_ranges_; - RangeSet source_ranges_; - std::string hash_algorithm_; - std::string salt_hex_; - std::string root_hash_; -}; - -// Command class holds the info for an update command that performs block-based OTA (BBOTA). Each -// command consists of one or several args, namely TargetInfo, SourceInfo, StashInfo and PatchInfo. -// The currently used BBOTA version is v4. -// -// zero -// - Fill the indicated blocks with zeros. -// - Meaningful args: TargetInfo -// -// new -// - Fill the blocks with data read from the new_data file. -// - Meaningful args: TargetInfo -// -// erase -// - Mark the given blocks as empty. -// - Meaningful args: TargetInfo -// -// move <...> -// - Read the source blocks, write result to target blocks. -// - Meaningful args: TargetInfo, SourceInfo -// -// See the note below for <...>. -// -// bsdiff <...> -// imgdiff <...> -// - Read the source blocks, apply a patch, and write result to target blocks. -// - Meaningful args: PatchInfo, TargetInfo, SourceInfo -// -// It expects <...> in one of the following formats: -// -// - <[stash_id:stash_location] ...> -// (loads data from stashes only) -// -// -// (loads data from source image only) -// -// -// <[stash_id:stash_location] ...> -// (loads data from both of source image and stashes) -// -// stash -// - Load the given source blocks and stash the data in the given slot of the stash table. -// - Meaningful args: StashInfo -// -// free -// - Free the given stash data. -// - Meaningful args: StashInfo -// -// compute_hash_tree -// - Computes the hash_tree bytes and writes the result to the specified range on the -// block_device. -// -// abort -// - Abort the current update. Allowed for testing code only. -// -class Command { - public: - enum class Type { - ABORT, - BSDIFF, - COMPUTE_HASH_TREE, - ERASE, - FREE, - IMGDIFF, - MOVE, - NEW, - STASH, - ZERO, - LAST, // Not a valid type. - }; - - Command() = default; - - Command(Type type, size_t index, std::string cmdline, PatchInfo patch, TargetInfo target, - SourceInfo source, StashInfo stash) - : type_(type), - index_(index), - cmdline_(std::move(cmdline)), - patch_(patch), - target_(std::move(target)), - source_(std::move(source)), - stash_(std::move(stash)) {} - - Command(Type type, size_t index, std::string cmdline, HashTreeInfo hash_tree_info); - - // Parses the given command 'line' into a Command object and returns it. The 'index' is specified - // by the caller to index the object. On parsing error, it returns an empty Command object that - // evaluates to false, and the specific error message will be set in 'err'. - static Command Parse(const std::string& line, size_t index, std::string* err); - - // Parses the command type from the given string. - static Type ParseType(const std::string& type_str); - - Type type() const { - return type_; - } - - size_t index() const { - return index_; - } - - const std::string& cmdline() const { - return cmdline_; - } - - const PatchInfo& patch() const { - return patch_; - } - - const TargetInfo& target() const { - return target_; - } - - const SourceInfo& source() const { - return source_; - } - - const StashInfo& stash() const { - return stash_; - } - - const HashTreeInfo& hash_tree_info() const { - return hash_tree_info_; - } - - size_t block_size() const { - return block_size_; - } - - constexpr explicit operator bool() const { - return type_ != Type::LAST; - } - - private: - friend class ResumableUpdaterTest; - friend class UpdaterTest; - - FRIEND_TEST(CommandsTest, Parse_ABORT_Allowed); - FRIEND_TEST(CommandsTest, Parse_InvalidNumberOfArgs); - FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput); - FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly); - FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes); - FRIEND_TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly); - - // Parses the target and source info from the given 'tokens' vector. Saves the parsed info into - // 'target' and 'source' objects. Returns the parsing result. Error message will be set in 'err' - // on parsing error, and the contents in 'target' and 'source' will be undefined. - static bool ParseTargetInfoAndSourceInfo(const std::vector& tokens, - const std::string& tgt_hash, TargetInfo* target, - const std::string& src_hash, SourceInfo* source, - std::string* err); - - // Allows parsing ABORT command, which should be used for testing purpose only. - static bool abort_allowed_; - - // The type of the command. - Type type_{ Type::LAST }; - // The index of the Command object, which is specified by the caller. - size_t index_{ 0 }; - // The input string that the Command object is parsed from. - std::string cmdline_; - // The patch info. Only meaningful for BSDIFF and IMGDIFF commands. - PatchInfo patch_; - // The target info, where the command should be written to. - TargetInfo target_; - // The source info to load the source blocks for the command. - SourceInfo source_; - // The stash info. Only meaningful for STASH and FREE commands. Note that although SourceInfo may - // also load data from stash, such info will be owned and managed by SourceInfo (i.e. in source_). - StashInfo stash_; - // The hash_tree info. Only meaningful for COMPUTE_HASH_TREE. - HashTreeInfo hash_tree_info_; - // The unit size of each block to be used in this command. - size_t block_size_{ 4096 }; -}; - -std::ostream& operator<<(std::ostream& os, const Command& command); - -// TransferList represents the info for a transfer list, which is parsed from input text lines -// containing commands to transfer data from one place to another on the target partition. -// -// The creator of the transfer list will guarantee that no block is read (i.e., used as the source -// for a patch or move) after it has been written. -// -// The creator will guarantee that a given stash is loaded (with a stash command) before it's used -// in a move/bsdiff/imgdiff command. -// -// Within one command the source and target ranges may overlap so in general we need to read the -// entire source into memory before writing anything to the target blocks. -// -// All the patch data is concatenated into one patch_data file in the update package. It must be -// stored uncompressed because we memory-map it in directly from the archive. (Since patches are -// already compressed, we lose very little by not compressing their concatenation.) -// -// Commands that read data from the partition (i.e. move/bsdiff/imgdiff/stash) have one or more -// additional hashes before the range parameters, which are used to check if the command has -// already been completed and verify the integrity of the source data. -class TransferList { - public: - // Number of header lines. - static constexpr size_t kTransferListHeaderLines = 4; - - TransferList() = default; - - // Parses the given input string and returns a TransferList object. Sets error message if any. - static TransferList Parse(const std::string& transfer_list_str, std::string* err); - - int version() const { - return version_; - } - - size_t total_blocks() const { - return total_blocks_; - } - - size_t stash_max_entries() const { - return stash_max_entries_; - } - - size_t stash_max_blocks() const { - return stash_max_blocks_; - } - - const std::vector& commands() const { - return commands_; - } - - // Returns whether the TransferList is valid. - constexpr explicit operator bool() const { - return version_ != 0; - } - - private: - // BBOTA version. - int version_{ 0 }; - // Total number of blocks to be written in this transfer. - size_t total_blocks_; - // Maximum number of stashes that exist at the same time. - size_t stash_max_entries_; - // Maximum number of blocks to be stashed. - size_t stash_max_blocks_; - // Commands in this transfer. - std::vector commands_; -}; diff --git a/updater/include/private/utils.h b/updater/include/private/utils.h deleted file mode 100644 index 33cf6155..00000000 --- a/updater/include/private/utils.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -bool SetUpdatedMarker(const std::string& marker); diff --git a/updater/include/updater/blockimg.h b/updater/include/updater/blockimg.h deleted file mode 100644 index 71733b30..00000000 --- a/updater/include/updater/blockimg.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _UPDATER_BLOCKIMG_H_ -#define _UPDATER_BLOCKIMG_H_ - -#include - -void RegisterBlockImageFunctions(); - -#endif diff --git a/updater/include/updater/build_info.h b/updater/include/updater/build_info.h deleted file mode 100644 index 0073bfa4..00000000 --- a/updater/include/updater/build_info.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -#include - -// This class serves as the aggregation of the fake block device information during update -// simulation on host. In specific, it has the name of the block device, its mount point, and the -// path to the temporary file that fakes this block device. -class FakeBlockDevice { - public: - FakeBlockDevice(std::string block_device, std::string mount_point, std::string temp_file_path) - : blockdev_name(std::move(block_device)), - mount_point(std::move(mount_point)), - mounted_file_path(std::move(temp_file_path)) {} - - std::string blockdev_name; - std::string mount_point; - std::string mounted_file_path; // path to the temp file that mocks the block device -}; - -// This class stores the information of the source build. For example, it creates and maintains -// the temporary files to simulate the block devices on host. Therefore, the simulator runtime can -// query the information and run the update on host. -class BuildInfo { - public: - BuildInfo(const std::string_view work_dir, bool keep_images) - : work_dir_(work_dir), keep_images_(keep_images) {} - // Returns the value of the build properties. - std::string GetProperty(const std::string_view key, const std::string_view default_value) const; - // Returns the path to the mock block device. - std::string FindBlockDeviceName(const std::string_view name) const; - // Parses the given target-file, initializes the build properties and extracts the images. - bool ParseTargetFile(const std::string_view target_file_path, bool extracted_input); - - std::string GetOemSettings() const { - return oem_settings_; - } - void SetOemSettings(const std::string_view oem_settings) { - oem_settings_ = oem_settings; - } - - private: - // A map to store the system properties during simulation. - std::map> build_props_; - // A file that contains the oem properties. - std::string oem_settings_; - // A map from the blockdev_name to the FakeBlockDevice object, which contains the path to the - // temporary file. - std::map> blockdev_map_; - - std::list temp_files_; - std::string work_dir_; // A temporary directory to store the extracted image files - bool keep_images_; -}; diff --git a/updater/include/updater/dynamic_partitions.h b/updater/include/updater/dynamic_partitions.h deleted file mode 100644 index 31cf859c..00000000 --- a/updater/include/updater/dynamic_partitions.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -void RegisterDynamicPartitionsFunctions(); diff --git a/updater/include/updater/install.h b/updater/include/updater/install.h deleted file mode 100644 index 9fe20314..00000000 --- a/updater/include/updater/install.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -void RegisterInstallFunctions(); diff --git a/updater/include/updater/simulator_runtime.h b/updater/include/updater/simulator_runtime.h deleted file mode 100644 index fa878db3..00000000 --- a/updater/include/updater/simulator_runtime.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "edify/updater_runtime_interface.h" -#include "updater/build_info.h" - -class SimulatorRuntime : public UpdaterRuntimeInterface { - public: - explicit SimulatorRuntime(BuildInfo* source) : source_(source) {} - - bool IsSimulator() const override { - return true; - } - - std::string GetProperty(const std::string_view key, - const std::string_view default_value) const override; - - int Mount(const std::string_view location, const std::string_view mount_point, - const std::string_view fs_type, const std::string_view mount_options) override; - bool IsMounted(const std::string_view mount_point) const override; - std::pair Unmount(const std::string_view mount_point) override; - - bool ReadFileToString(const std::string_view filename, std::string* content) const override; - bool WriteStringToFile(const std::string_view content, - const std::string_view filename) const override; - - int WipeBlockDevice(const std::string_view filename, size_t len) const override; - int RunProgram(const std::vector& args, bool is_vfork) const override; - int Tune2Fs(const std::vector& args) const override; - - bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override; - bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override; - bool UpdateDynamicPartitions(const std::string_view op_list_value) override; - std::string AddSlotSuffix(const std::string_view arg) const override; - - private: - std::string FindBlockDeviceName(const std::string_view name) const override; - - BuildInfo* source_; - std::map> mounted_partitions_; -}; diff --git a/updater/include/updater/target_files.h b/updater/include/updater/target_files.h deleted file mode 100644 index 860d47a3..00000000 --- a/updater/include/updater/target_files.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include - -#include -#include - -// This class represents the mount information for each line in a fstab file. -class FstabInfo { - public: - FstabInfo(std::string blockdev_name, std::string mount_point, std::string fs_type) - : blockdev_name(std::move(blockdev_name)), - mount_point(std::move(mount_point)), - fs_type(std::move(fs_type)) {} - - std::string blockdev_name; - std::string mount_point; - std::string fs_type; -}; - -// This class parses a target file from a zip file or an extracted directory. It also provides the -// function to read the its content for simulation. -class TargetFile { - public: - TargetFile(std::string path, bool extracted_input) - : path_(std::move(path)), extracted_input_(extracted_input) {} - - // Opens the input target file (or extracted directory) and parses the misc_info.txt. - bool Open(); - // Parses the build properties in all possible locations and save them in |props_map| - bool GetBuildProps(std::map>* props_map) const; - // Parses the fstab and save the information about each partition to mount into |fstab_info_list|. - bool ParseFstabInfo(std::vector* fstab_info_list) const; - // Returns true if the given entry exists in the target file. - bool EntryExists(const std::string_view name) const; - // Extracts the image file |entry_name|. Returns true on success. - bool ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info, - const std::string_view work_dir, TemporaryFile* image_file) const; - - private: - // Wrapper functions to read the entry from either the zipped target-file, or the extracted input - // directory. - bool ReadEntryToString(const std::string_view name, std::string* content) const; - bool ExtractEntryToTempFile(const std::string_view name, TemporaryFile* temp_file) const; - - std::string path_; // Path to the zipped target-file or an extracted directory. - bool extracted_input_; // True if the target-file has been extracted. - ZipArchiveHandle handle_{ nullptr }; - - // The properties under META/misc_info.txt - std::map> misc_info_; -}; diff --git a/updater/include/updater/updater.h b/updater/include/updater/updater.h deleted file mode 100644 index 8676b603..00000000 --- a/updater/include/updater/updater.h +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -#include -#include -#include - -#include - -#include "edify/expr.h" -#include "edify/updater_interface.h" -#include "otautil/error_code.h" -#include "otautil/sysutil.h" - -class Updater : public UpdaterInterface { - public: - explicit Updater(std::unique_ptr run_time) - : runtime_(std::move(run_time)) {} - - ~Updater() override; - - // Memory-maps the OTA package and opens it as a zip file. Also sets up the command pipe and - // UpdaterRuntime. - bool Init(int fd, const std::string_view package_filename, bool is_retry); - - // Parses and evaluates the updater-script in the OTA package. Reports the error code if the - // evaluation fails. - bool RunUpdate(); - - // Writes the message to command pipe, adds a new line in the end. - void WriteToCommandPipe(const std::string_view message, bool flush = false) const override; - - // Sends over the message to recovery to print it on the screen. - void UiPrint(const std::string_view message) const override; - - std::string FindBlockDeviceName(const std::string_view name) const override; - - UpdaterRuntimeInterface* GetRuntime() const override { - return runtime_.get(); - } - ZipArchiveHandle GetPackageHandle() const override { - return package_handle_; - } - std::string GetResult() const override { - return result_; - } - uint8_t* GetMappedPackageAddress() const override { - return mapped_package_.addr; - } - size_t GetMappedPackageLength() const override { - return mapped_package_.length; - } - - private: - friend class UpdaterTestBase; - friend class UpdaterTest; - // Where in the package we expect to find the edify script to execute. - // (Note it's "updateR-script", not the older "update-script".) - static constexpr const char* SCRIPT_NAME = "META-INF/com/google/android/updater-script"; - - // Reads the entry |name| in the zip archive and put the result in |content|. - bool ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name, std::string* content); - - // Parses the error code embedded in state->errmsg; and reports the error code and cause code. - void ParseAndReportErrorCode(State* state); - - std::unique_ptr runtime_; - - MemMapping mapped_package_; - ZipArchiveHandle package_handle_{ nullptr }; - std::string updater_script_; - - bool is_retry_{ false }; - std::unique_ptr cmd_pipe_{ nullptr, fclose }; - - std::string result_; - std::vector skipped_functions_; -}; diff --git a/updater/include/updater/updater_runtime.h b/updater/include/updater/updater_runtime.h deleted file mode 100644 index b943dfcf..00000000 --- a/updater/include/updater/updater_runtime.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "edify/updater_runtime_interface.h" - -struct selabel_handle; - -class UpdaterRuntime : public UpdaterRuntimeInterface { - public: - explicit UpdaterRuntime(struct selabel_handle* sehandle) : sehandle_(sehandle) {} - ~UpdaterRuntime() override = default; - - bool IsSimulator() const override { - return false; - } - - std::string GetProperty(const std::string_view key, - const std::string_view default_value) const override; - - std::string FindBlockDeviceName(const std::string_view name) const override; - - int Mount(const std::string_view location, const std::string_view mount_point, - const std::string_view fs_type, const std::string_view mount_options) override; - bool IsMounted(const std::string_view mount_point) const override; - std::pair Unmount(const std::string_view mount_point) override; - - bool ReadFileToString(const std::string_view filename, std::string* content) const override; - bool WriteStringToFile(const std::string_view content, - const std::string_view filename) const override; - - int WipeBlockDevice(const std::string_view filename, size_t len) const override; - int RunProgram(const std::vector& args, bool is_vfork) const override; - int Tune2Fs(const std::vector& args) const override; - - bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) override; - bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) override; - bool UpdateDynamicPartitions(const std::string_view op_list_value) override; - std::string AddSlotSuffix(const std::string_view arg) const override; - - private: - struct selabel_handle* sehandle_{ nullptr }; -}; diff --git a/updater/install.cpp b/updater/install.cpp deleted file mode 100644 index 29596504..00000000 --- a/updater/install.cpp +++ /dev/null @@ -1,910 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "updater/install.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "edify/expr.h" -#include "edify/updater_interface.h" -#include "edify/updater_runtime_interface.h" -#include "otautil/dirutil.h" -#include "otautil/error_code.h" -#include "otautil/print_sha1.h" -#include "otautil/sysutil.h" - -#ifndef __ANDROID__ -#include // for strlcpy -#endif - -static bool UpdateBlockDeviceNameForPartition(UpdaterInterface* updater, Partition* partition) { - CHECK(updater); - std::string name = updater->FindBlockDeviceName(partition->name); - if (name.empty()) { - LOG(ERROR) << "Failed to find the block device " << partition->name; - return false; - } - - partition->name = std::move(name); - return true; -} - -// This is the updater side handler for ui_print() in edify script. Contents will be sent over to -// the recovery side for on-screen display. -Value* UIPrintFn(const char* name, State* state, const std::vector>& argv) { - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); - } - - std::string buffer = android::base::Join(args, ""); - state->updater->UiPrint(buffer); - return StringValue(buffer); -} - -// package_extract_file(package_file[, dest_file]) -// Extracts a single package_file from the update package and writes it to dest_file, -// overwriting existing files if necessary. Without the dest_file argument, returns the -// contents of the package file as a binary blob. -Value* PackageExtractFileFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() < 1 || argv.size() > 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 or 2 args, got %zu", name, - argv.size()); - } - - if (argv.size() == 2) { - // The two-argument version extracts to a file. - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name, - argv.size()); - } - const std::string& zip_path = args[0]; - std::string dest_path = args[1]; - - ZipArchiveHandle za = state->updater->GetPackageHandle(); - ZipEntry64 entry; - if (FindEntry(za, zip_path, &entry) != 0) { - LOG(ERROR) << name << ": no " << zip_path << " in package"; - return StringValue(""); - } - - // Update the destination of package_extract_file if it's a block device. During simulation the - // destination will map to a fake file. - if (std::string block_device_name = state->updater->FindBlockDeviceName(dest_path); - !block_device_name.empty()) { - dest_path = block_device_name; - } - - android::base::unique_fd fd(TEMP_FAILURE_RETRY( - open(dest_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR))); - if (fd == -1) { - PLOG(ERROR) << name << ": can't open " << dest_path << " for write"; - return StringValue(""); - } - - bool success = true; - int32_t ret = ExtractEntryToFile(za, &entry, fd); - if (ret != 0) { - LOG(ERROR) << name << ": Failed to extract entry \"" << zip_path << "\" (" - << entry.uncompressed_length << " bytes) to \"" << dest_path - << "\": " << ErrorCodeString(ret); - success = false; - } - if (fsync(fd) == -1) { - PLOG(ERROR) << "fsync of \"" << dest_path << "\" failed"; - success = false; - } - - if (close(fd.release()) != 0) { - PLOG(ERROR) << "close of \"" << dest_path << "\" failed"; - success = false; - } - - return StringValue(success ? "t" : ""); - } else { - // The one-argument version returns the contents of the file as the result. - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse %zu args", name, - argv.size()); - } - const std::string& zip_path = args[0]; - - ZipArchiveHandle za = state->updater->GetPackageHandle(); - ZipEntry64 entry; - if (FindEntry(za, zip_path, &entry) != 0) { - return ErrorAbort(state, kPackageExtractFileFailure, "%s(): no %s in package", name, - zip_path.c_str()); - } - - std::string buffer; - if (entry.uncompressed_length > std::numeric_limits::max()) { - return ErrorAbort(state, kPackageExtractFileFailure, - "%s(): Entry `%s` Uncompressed size exceeds size of address space.", name, - zip_path.c_str()); - } - buffer.resize(entry.uncompressed_length); - - int32_t ret = - ExtractToMemory(za, &entry, reinterpret_cast(&buffer[0]), buffer.size()); - if (ret != 0) { - return ErrorAbort(state, kPackageExtractFileFailure, - "%s: Failed to extract entry \"%s\" (%zu bytes) to memory: %s", name, - zip_path.c_str(), buffer.size(), ErrorCodeString(ret)); - } - - return new Value(Value::Type::BLOB, buffer); - } -} - -// patch_partition_check(target_partition, source_partition) -// Checks if the target and source partitions have the desired checksums to be patched. It returns -// directly, if the target partition already has the expected checksum. Otherwise it in turn -// checks the integrity of the source partition and the backup file on /cache. -// -// For example, patch_partition_check( -// "EMMC:/dev/block/boot:12342568:8aaacf187a6929d0e9c3e9e46ea7ff495b43424d", -// "EMMC:/dev/block/boot:12363048:06b0b16299dcefc94900efed01e0763ff644ffa4") -Value* PatchPartitionCheckFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 2) { - return ErrorAbort(state, kArgsParsingFailure, - "%s(): Invalid number of args (expected 2, got %zu)", name, argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args, 0, 2)) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); - } - - std::string err; - auto target = Partition::Parse(args[0], &err); - if (!target) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse target \"%s\": %s", name, - args[0].c_str(), err.c_str()); - } - - auto source = Partition::Parse(args[1], &err); - if (!source) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse source \"%s\": %s", name, - args[1].c_str(), err.c_str()); - } - - if (!UpdateBlockDeviceNameForPartition(state->updater, &source) || - !UpdateBlockDeviceNameForPartition(state->updater, &target)) { - return StringValue(""); - } - - bool result = PatchPartitionCheck(target, source); - return StringValue(result ? "t" : ""); -} - -// patch_partition(target, source, patch) -// Applies the given patch to the source partition, and writes the result to the target partition. -// -// For example, patch_partition( -// "EMMC:/dev/block/boot:12342568:8aaacf187a6929d0e9c3e9e46ea7ff495b43424d", -// "EMMC:/dev/block/boot:12363048:06b0b16299dcefc94900efed01e0763ff644ffa4", -// package_extract_file("boot.img.p")) -Value* PatchPartitionFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 3) { - return ErrorAbort(state, kArgsParsingFailure, - "%s(): Invalid number of args (expected 3, got %zu)", name, argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args, 0, 2)) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); - } - - std::string err; - auto target = Partition::Parse(args[0], &err); - if (!target) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse target \"%s\": %s", name, - args[0].c_str(), err.c_str()); - } - - auto source = Partition::Parse(args[1], &err); - if (!source) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse source \"%s\": %s", name, - args[1].c_str(), err.c_str()); - } - - std::vector> values; - if (!ReadValueArgs(state, argv, &values, 2, 1) || values[0]->type != Value::Type::BLOB) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Invalid patch arg", name); - } - - if (!UpdateBlockDeviceNameForPartition(state->updater, &source) || - !UpdateBlockDeviceNameForPartition(state->updater, &target)) { - return StringValue(""); - } - - bool result = PatchPartition(target, source, *values[0], nullptr, true); - return StringValue(result ? "t" : ""); -} - -// mount(fs_type, partition_type, location, mount_point) -// mount(fs_type, partition_type, location, mount_point, mount_options) - -// fs_type="ext4" partition_type="EMMC" location=device -Value* MountFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 4 && argv.size() != 5) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 4-5 args, got %zu", name, - argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& fs_type = args[0]; - const std::string& partition_type = args[1]; - const std::string& location = args[2]; - const std::string& mount_point = args[3]; - std::string mount_options; - - if (argv.size() == 5) { - mount_options = args[4]; - } - - if (fs_type.empty()) { - return ErrorAbort(state, kArgsParsingFailure, "fs_type argument to %s() can't be empty", name); - } - if (partition_type.empty()) { - return ErrorAbort(state, kArgsParsingFailure, "partition_type argument to %s() can't be empty", - name); - } - if (location.empty()) { - return ErrorAbort(state, kArgsParsingFailure, "location argument to %s() can't be empty", name); - } - if (mount_point.empty()) { - return ErrorAbort(state, kArgsParsingFailure, "mount_point argument to %s() can't be empty", - name); - } - - auto updater = state->updater; - if (updater->GetRuntime()->Mount(location, mount_point, fs_type, mount_options) != 0) { - updater->UiPrint(android::base::StringPrintf("%s: Failed to mount %s at %s: %s", name, - location.c_str(), mount_point.c_str(), - strerror(errno))); - return StringValue(""); - } - - return StringValue(mount_point); -} - -// is_mounted(mount_point) -Value* IsMountedFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& mount_point = args[0]; - if (mount_point.empty()) { - return ErrorAbort(state, kArgsParsingFailure, - "mount_point argument to unmount() can't be empty"); - } - - auto updater_runtime = state->updater->GetRuntime(); - if (!updater_runtime->IsMounted(mount_point)) { - return StringValue(""); - } - - return StringValue(mount_point); -} - -Value* UnmountFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); - } - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& mount_point = args[0]; - if (mount_point.empty()) { - return ErrorAbort(state, kArgsParsingFailure, - "mount_point argument to unmount() can't be empty"); - } - - auto updater = state->updater; - auto [mounted, result] = updater->GetRuntime()->Unmount(mount_point); - if (!mounted) { - updater->UiPrint( - android::base::StringPrintf("Failed to unmount %s: No such volume", mount_point.c_str())); - return nullptr; - } else if (result != 0) { - updater->UiPrint(android::base::StringPrintf("Failed to unmount %s: %s", mount_point.c_str(), - strerror(errno))); - } - - return StringValue(mount_point); -} - -// format(fs_type, partition_type, location, fs_size, mount_point) -// -// fs_type="ext4" partition_type="EMMC" location=device fs_size= mount_point= -// fs_type="f2fs" partition_type="EMMC" location=device fs_size= mount_point= -// if fs_size == 0, then make fs uses the entire partition. -// if fs_size > 0, that is the size to use -// if fs_size < 0, then reserve that many bytes at the end of the partition (not for "f2fs") -Value* FormatFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 5) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 5 args, got %zu", name, - argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& fs_type = args[0]; - const std::string& partition_type = args[1]; - const std::string& location = args[2]; - const std::string& fs_size = args[3]; - const std::string& mount_point = args[4]; - - if (fs_type.empty()) { - return ErrorAbort(state, kArgsParsingFailure, "fs_type argument to %s() can't be empty", name); - } - if (partition_type.empty()) { - return ErrorAbort(state, kArgsParsingFailure, "partition_type argument to %s() can't be empty", - name); - } - if (location.empty()) { - return ErrorAbort(state, kArgsParsingFailure, "location argument to %s() can't be empty", name); - } - if (mount_point.empty()) { - return ErrorAbort(state, kArgsParsingFailure, "mount_point argument to %s() can't be empty", - name); - } - - int64_t size; - if (!android::base::ParseInt(fs_size, &size)) { - return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s", name, - fs_size.c_str()); - } - - auto updater_runtime = state->updater->GetRuntime(); - if (fs_type == "ext4") { - std::vector mke2fs_args = { - "/system/bin/mke2fs", "-t", "ext4", "-b", "4096", location - }; - if (size != 0) { - mke2fs_args.push_back(std::to_string(size / 4096LL)); - } - - if (auto status = updater_runtime->RunProgram(mke2fs_args, true); status != 0) { - LOG(ERROR) << name << ": mke2fs failed (" << status << ") on " << location; - return StringValue(""); - } - - if (auto status = updater_runtime->RunProgram( - { "/system/bin/e2fsdroid", "-e", "-a", mount_point, location }, true); - status != 0) { - LOG(ERROR) << name << ": e2fsdroid failed (" << status << ") on " << location; - return StringValue(""); - } - return StringValue(location); - } - - if (fs_type == "f2fs") { - if (size < 0) { - LOG(ERROR) << name << ": fs_size can't be negative for f2fs: " << fs_size; - return StringValue(""); - } - std::vector f2fs_args = { - "/system/bin/make_f2fs", "-g", "android", "-w", "512", location - }; - if (size >= 512) { - f2fs_args.push_back(std::to_string(size / 512)); - } - if (auto status = updater_runtime->RunProgram(f2fs_args, true); status != 0) { - LOG(ERROR) << name << ": make_f2fs failed (" << status << ") on " << location; - return StringValue(""); - } - - if (auto status = updater_runtime->RunProgram( - { "/system/bin/sload_f2fs", "-t", mount_point, location }, true); - status != 0) { - LOG(ERROR) << name << ": sload_f2fs failed (" << status << ") on " << location; - return StringValue(""); - } - - return StringValue(location); - } - - LOG(ERROR) << name << ": unsupported fs_type \"" << fs_type << "\" partition_type \"" - << partition_type << "\""; - return nullptr; -} - -Value* ShowProgressFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, - argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& frac_str = args[0]; - const std::string& sec_str = args[1]; - - double frac; - if (!android::base::ParseDouble(frac_str.c_str(), &frac)) { - return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s", name, - frac_str.c_str()); - } - int sec; - if (!android::base::ParseInt(sec_str.c_str(), &sec)) { - return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse int in %s", name, - sec_str.c_str()); - } - - state->updater->WriteToCommandPipe(android::base::StringPrintf("progress %f %d", frac, sec)); - - return StringValue(frac_str); -} - -Value* SetProgressFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& frac_str = args[0]; - - double frac; - if (!android::base::ParseDouble(frac_str.c_str(), &frac)) { - return ErrorAbort(state, kArgsParsingFailure, "%s: failed to parse double in %s", name, - frac_str.c_str()); - } - - state->updater->WriteToCommandPipe(android::base::StringPrintf("set_progress %f", frac)); - - return StringValue(frac_str); -} - -Value* GetPropFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); - } - std::string key; - if (!Evaluate(state, argv[0], &key)) { - return nullptr; - } - - auto updater_runtime = state->updater->GetRuntime(); - std::string value = updater_runtime->GetProperty(key, ""); - - return StringValue(value); -} - -// file_getprop(file, key) -// -// interprets 'file' as a getprop-style file (key=value pairs, one -// per line. # comment lines, blank lines, lines without '=' ignored), -// and returns the value for 'key' (or "" if it isn't defined). -Value* FileGetPropFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, - argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& filename = args[0]; - const std::string& key = args[1]; - - std::string buffer; - auto updater_runtime = state->updater->GetRuntime(); - if (!updater_runtime->ReadFileToString(filename, &buffer)) { - ErrorAbort(state, kFreadFailure, "%s: failed to read %s", name, filename.c_str()); - return nullptr; - } - - std::vector lines = android::base::Split(buffer, "\n"); - for (size_t i = 0; i < lines.size(); i++) { - std::string line = android::base::Trim(lines[i]); - - // comment or blank line: skip to next line - if (line.empty() || line[0] == '#') { - continue; - } - size_t equal_pos = line.find('='); - if (equal_pos == std::string::npos) { - continue; - } - - // trim whitespace between key and '=' - std::string str = android::base::Trim(line.substr(0, equal_pos)); - - // not the key we're looking for - if (key != str) continue; - - return StringValue(android::base::Trim(line.substr(equal_pos + 1))); - } - - return StringValue(""); -} - -// apply_patch_space(bytes) -Value* ApplyPatchSpaceFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 args, got %zu", name, - argv.size()); - } - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& bytes_str = args[0]; - - size_t bytes; - if (!android::base::ParseUint(bytes_str.c_str(), &bytes)) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): can't parse \"%s\" as byte count", name, - bytes_str.c_str()); - } - - // Skip the cache size check if the update is a retry. - if (state->is_retry || CheckAndFreeSpaceOnCache(bytes)) { - return StringValue("t"); - } - return StringValue(""); -} - -Value* WipeCacheFn(const char* name, State* state, const std::vector>& argv) { - if (!argv.empty()) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name, - argv.size()); - } - - state->updater->WriteToCommandPipe("wipe_cache"); - return StringValue("t"); -} - -Value* RunProgramFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() < 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - - auto updater_runtime = state->updater->GetRuntime(); - auto status = updater_runtime->RunProgram(args, false); - return StringValue(std::to_string(status)); -} - -// read_file(filename) -// Reads a local file 'filename' and returns its contents as a string Value. -Value* ReadFileFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); - } - const std::string& filename = args[0]; - - std::string contents; - auto updater_runtime = state->updater->GetRuntime(); - if (updater_runtime->ReadFileToString(filename, &contents)) { - return new Value(Value::Type::STRING, std::move(contents)); - } - - // Leave it to caller to handle the failure. - PLOG(ERROR) << name << ": Failed to read " << filename; - return StringValue(""); -} - -// write_value(value, filename) -// Writes 'value' to 'filename'. -// Example: write_value("960000", "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq") -Value* WriteValueFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, - argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); - } - - const std::string& filename = args[1]; - if (filename.empty()) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Filename cannot be empty", name); - } - - const std::string& value = args[0]; - auto updater_runtime = state->updater->GetRuntime(); - if (!updater_runtime->WriteStringToFile(value, filename)) { - PLOG(ERROR) << name << ": Failed to write to \"" << filename << "\""; - return StringValue(""); - } - return StringValue("t"); -} - -// Immediately reboot the device. Recovery is not finished normally, -// so if you reboot into recovery it will re-start applying the -// current package (because nothing has cleared the copy of the -// arguments stored in the BCB). -// -// The argument is the partition name passed to the android reboot -// property. It can be "recovery" to boot from the recovery -// partition, or "" (empty string) to boot from the regular boot -// partition. -Value* RebootNowFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, - argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); - } - const std::string& filename = args[0]; - const std::string& property = args[1]; - - // Zero out the 'command' field of the bootloader message. Leave the rest intact. - bootloader_message boot; - std::string err; - if (!read_bootloader_message_from(&boot, filename, &err)) { - LOG(ERROR) << name << "(): Failed to read from \"" << filename << "\": " << err; - return StringValue(""); - } - memset(boot.command, 0, sizeof(boot.command)); - if (!write_bootloader_message_to(boot, filename, &err)) { - LOG(ERROR) << name << "(): Failed to write to \"" << filename << "\": " << err; - return StringValue(""); - } - - Reboot(property); - - return ErrorAbort(state, kRebootFailure, "%s() failed to reboot", name); -} - -// Store a string value somewhere that future invocations of recovery -// can access it. This value is called the "stage" and can be used to -// drive packages that need to do reboots in the middle of -// installation and keep track of where they are in the multi-stage -// install. -// -// The first argument is the block device for the misc partition -// ("/misc" in the fstab), which is where this value is stored. The -// second argument is the string to store; it should not exceed 31 -// bytes. -Value* SetStageFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, - argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& filename = args[0]; - const std::string& stagestr = args[1]; - - // Store this value in the misc partition, immediately after the - // bootloader message that the main recovery uses to save its - // arguments in case of the device restarting midway through - // package installation. - bootloader_message boot; - std::string err; - if (!read_bootloader_message_from(&boot, filename, &err)) { - LOG(ERROR) << name << "(): Failed to read from \"" << filename << "\": " << err; - return StringValue(""); - } - strlcpy(boot.stage, stagestr.c_str(), sizeof(boot.stage)); - if (!write_bootloader_message_to(boot, filename, &err)) { - LOG(ERROR) << name << "(): Failed to write to \"" << filename << "\": " << err; - return StringValue(""); - } - - return StringValue(filename); -} - -// Return the value most recently saved with SetStageFn. The argument -// is the block device for the misc partition. -Value* GetStageFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& filename = args[0]; - - bootloader_message boot; - std::string err; - if (!read_bootloader_message_from(&boot, filename, &err)) { - LOG(ERROR) << name << "(): Failed to read from \"" << filename << "\": " << err; - return StringValue(""); - } - - return StringValue(boot.stage); -} - -Value* WipeBlockDeviceFn(const char* name, State* state, const std::vector>& argv) { - if (argv.size() != 2) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, - argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& filename = args[0]; - const std::string& len_str = args[1]; - - size_t len; - if (!android::base::ParseUint(len_str.c_str(), &len)) { - return nullptr; - } - - auto updater_runtime = state->updater->GetRuntime(); - int status = updater_runtime->WipeBlockDevice(filename, len); - return StringValue(status == 0 ? "t" : ""); -} - -Value* EnableRebootFn(const char* name, State* state, const std::vector>& argv) { - if (!argv.empty()) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects no args, got %zu", name, - argv.size()); - } - state->updater->WriteToCommandPipe("enable_reboot"); - return StringValue("t"); -} - -Value* Tune2FsFn(const char* name, State* state, const std::vector>& argv) { - if (argv.empty()) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects args, got %zu", name, argv.size()); - } - - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() could not read args", name); - } - - // tune2fs expects the program name as its first arg. - args.insert(args.begin(), "tune2fs"); - auto updater_runtime = state->updater->GetRuntime(); - if (auto result = updater_runtime->Tune2Fs(args); result != 0) { - return ErrorAbort(state, kTune2FsFailure, "%s() returned error code %d", name, result); - } - return StringValue("t"); -} - -Value* AddSlotSuffixFn(const char* name, State* state, - const std::vector>& argv) { - if (argv.size() != 1) { - return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); - } - std::vector args; - if (!ReadArgs(state, argv, &args)) { - return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); - } - const std::string& arg = args[0]; - auto updater_runtime = state->updater->GetRuntime(); - return StringValue(updater_runtime->AddSlotSuffix(arg)); -} - -void RegisterInstallFunctions() { - RegisterFunction("mount", MountFn); - RegisterFunction("is_mounted", IsMountedFn); - RegisterFunction("unmount", UnmountFn); - RegisterFunction("format", FormatFn); - RegisterFunction("show_progress", ShowProgressFn); - RegisterFunction("set_progress", SetProgressFn); - RegisterFunction("package_extract_file", PackageExtractFileFn); - - RegisterFunction("getprop", GetPropFn); - RegisterFunction("file_getprop", FileGetPropFn); - - RegisterFunction("apply_patch_space", ApplyPatchSpaceFn); - RegisterFunction("patch_partition", PatchPartitionFn); - RegisterFunction("patch_partition_check", PatchPartitionCheckFn); - - RegisterFunction("wipe_block_device", WipeBlockDeviceFn); - - RegisterFunction("read_file", ReadFileFn); - RegisterFunction("write_value", WriteValueFn); - - RegisterFunction("wipe_cache", WipeCacheFn); - - RegisterFunction("ui_print", UIPrintFn); - - RegisterFunction("run_program", RunProgramFn); - - RegisterFunction("reboot_now", RebootNowFn); - RegisterFunction("get_stage", GetStageFn); - RegisterFunction("set_stage", SetStageFn); - - RegisterFunction("enable_reboot", EnableRebootFn); - RegisterFunction("tune2fs", Tune2FsFn); - - RegisterFunction("add_slot_suffix", AddSlotSuffixFn); -} diff --git a/updater/mounts.cpp b/updater/mounts.cpp deleted file mode 100644 index 943d35c7..00000000 --- a/updater/mounts.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "mounts.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -struct MountedVolume { - std::string device; - std::string mount_point; - std::string filesystem; - std::string flags; -}; - -static std::vector g_mounts_state; - -bool scan_mounted_volumes() { - for (size_t i = 0; i < g_mounts_state.size(); ++i) { - delete g_mounts_state[i]; - } - g_mounts_state.clear(); - - // Open and read mount table entries. - FILE* fp = setmntent("/proc/mounts", "re"); - if (fp == NULL) { - return false; - } - mntent* e; - while ((e = getmntent(fp)) != NULL) { - MountedVolume* v = new MountedVolume; - v->device = e->mnt_fsname; - v->mount_point = e->mnt_dir; - v->filesystem = e->mnt_type; - v->flags = e->mnt_opts; - g_mounts_state.push_back(v); - } - endmntent(fp); - return true; -} - -MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point) { - for (size_t i = 0; i < g_mounts_state.size(); ++i) { - if (g_mounts_state[i]->mount_point == mount_point) return g_mounts_state[i]; - } - return nullptr; -} - -int unmount_mounted_volume(MountedVolume* volume) { - // Intentionally pass the empty string to umount if the caller tries to unmount a volume they - // already unmounted using this function. - std::string mount_point = volume->mount_point; - volume->mount_point.clear(); - int result = umount(mount_point.c_str()); - if (result == -1) { - PLOG(WARNING) << "Failed to umount " << mount_point; - } - return result; -} diff --git a/updater/mounts.h b/updater/mounts.h deleted file mode 100644 index 6786c8d2..00000000 --- a/updater/mounts.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -struct MountedVolume; - -bool scan_mounted_volumes(); - -MountedVolume* find_mounted_volume_by_mount_point(const char* mount_point); - -int unmount_mounted_volume(MountedVolume* volume); diff --git a/updater/simulator_runtime.cpp b/updater/simulator_runtime.cpp deleted file mode 100644 index 57dfb32d..00000000 --- a/updater/simulator_runtime.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "updater/simulator_runtime.h" - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "mounts.h" -#include "otautil/sysutil.h" - -std::string SimulatorRuntime::GetProperty(const std::string_view key, - const std::string_view default_value) const { - return source_->GetProperty(key, default_value); -} - -int SimulatorRuntime::Mount(const std::string_view location, const std::string_view mount_point, - const std::string_view /* fs_type */, - const std::string_view /* mount_options */) { - if (auto mounted_location = mounted_partitions_.find(mount_point); - mounted_location != mounted_partitions_.end() && mounted_location->second != location) { - LOG(ERROR) << mount_point << " has been mounted at " << mounted_location->second; - return -1; - } - - mounted_partitions_.emplace(mount_point, location); - return 0; -} - -bool SimulatorRuntime::IsMounted(const std::string_view mount_point) const { - return mounted_partitions_.find(mount_point) != mounted_partitions_.end(); -} - -std::pair SimulatorRuntime::Unmount(const std::string_view mount_point) { - if (!IsMounted(mount_point)) { - return { false, -1 }; - } - - mounted_partitions_.erase(std::string(mount_point)); - return { true, 0 }; -} - -std::string SimulatorRuntime::FindBlockDeviceName(const std::string_view name) const { - return source_->FindBlockDeviceName(name); -} - -// TODO(xunchang) implement the utility functions in simulator. -int SimulatorRuntime::RunProgram(const std::vector& args, bool /* is_vfork */) const { - LOG(INFO) << "Running program with args " << android::base::Join(args, " "); - return 0; -} - -int SimulatorRuntime::Tune2Fs(const std::vector& args) const { - LOG(INFO) << "Running Tune2Fs with args " << android::base::Join(args, " "); - return 0; -} - -int SimulatorRuntime::WipeBlockDevice(const std::string_view filename, size_t /* len */) const { - LOG(INFO) << "SKip wiping block device " << filename; - return 0; -} - -bool SimulatorRuntime::ReadFileToString(const std::string_view filename, - std::string* content) const { - if (android::base::EndsWith(filename, "oem.prop")) { - return android::base::ReadFileToString(source_->GetOemSettings(), content); - } - - LOG(INFO) << "SKip reading filename " << filename; - return true; -} - -bool SimulatorRuntime::WriteStringToFile(const std::string_view content, - const std::string_view filename) const { - LOG(INFO) << "SKip writing " << content.size() << " bytes to file " << filename; - return true; -} - -bool SimulatorRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name, - std::string* path) { - *path = partition_name; - return true; -} - -bool SimulatorRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) { - LOG(INFO) << "Skip unmapping " << partition_name; - return true; -} - -bool SimulatorRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) { - const std::unordered_set commands{ - "resize", "remove", "add", "move", - "add_group", "resize_group", "remove_group", "remove_all_groups", - }; - - std::vector lines = android::base::Split(std::string(op_list_value), "\n"); - for (const auto& line : lines) { - if (line.empty() || line[0] == '#') continue; - auto tokens = android::base::Split(line, " "); - if (commands.find(tokens[0]) == commands.end()) { - LOG(ERROR) << "Unknown operation in op_list: " << line; - return false; - } - } - return true; -} - -std::string SimulatorRuntime::AddSlotSuffix(const std::string_view arg) const { - LOG(INFO) << "Skip adding slot suffix to " << arg; - return std::string(arg); -} diff --git a/updater/target_files.cpp b/updater/target_files.cpp deleted file mode 100644 index 207146f5..00000000 --- a/updater/target_files.cpp +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "updater/target_files.h" - -#include - -#include -#include -#include - -#include -#include -#include - -static bool SimgToImg(int input_fd, int output_fd) { - if (lseek64(input_fd, 0, SEEK_SET) == -1) { - PLOG(ERROR) << "Failed to lseek64 on the input sparse image"; - return false; - } - - if (lseek64(output_fd, 0, SEEK_SET) == -1) { - PLOG(ERROR) << "Failed to lseek64 on the output raw image"; - return false; - } - - std::unique_ptr s_file( - sparse_file_import(input_fd, true, false), sparse_file_destroy); - if (!s_file) { - LOG(ERROR) << "Failed to import the sparse image."; - return false; - } - - if (sparse_file_write(s_file.get(), output_fd, false, false, false) < 0) { - PLOG(ERROR) << "Failed to output the raw image file."; - return false; - } - - return true; -} - -static bool ParsePropertyFile(const std::string_view prop_content, - std::map>* props_map) { - LOG(INFO) << "Start parsing build property\n"; - std::vector lines = android::base::Split(std::string(prop_content), "\n"); - for (const auto& line : lines) { - if (line.empty() || line[0] == '#') continue; - auto pos = line.find('='); - if (pos == std::string::npos) continue; - std::string key = line.substr(0, pos); - std::string value = line.substr(pos + 1); - LOG(INFO) << key << ": " << value; - props_map->emplace(key, value); - } - - return true; -} - -static bool ParseFstab(const std::string_view fstab, std::vector* fstab_info_list) { - LOG(INFO) << "parsing fstab\n"; - std::vector lines = android::base::Split(std::string(fstab), "\n"); - for (const auto& line : lines) { - if (line.empty() || line[0] == '#') continue; - - // optional: - std::vector tokens = android::base::Split(line, " "); - tokens.erase(std::remove(tokens.begin(), tokens.end(), ""), tokens.end()); - if (tokens.size() != 4 && tokens.size() != 5) { - LOG(ERROR) << "Unexpected token size: " << tokens.size() << std::endl - << "Error parsing fstab line: " << line; - return false; - } - - const auto& blockdev = tokens[0]; - const auto& mount_point = tokens[1]; - const auto& fs_type = tokens[2]; - if (!android::base::StartsWith(mount_point, "/")) { - LOG(WARNING) << "mount point '" << mount_point << "' does not start with '/'"; - continue; - } - - // The simulator only supports ext4 and emmc for now. - if (fs_type != "ext4" && fs_type != "emmc") { - LOG(WARNING) << "Unsupported fs_type in " << line; - continue; - } - - fstab_info_list->emplace_back(blockdev, mount_point, fs_type); - } - - return true; -} - -bool TargetFile::EntryExists(const std::string_view name) const { - if (extracted_input_) { - std::string entry_path = path_ + "/" + std::string(name); - if (access(entry_path.c_str(), O_RDONLY) != 0) { - PLOG(WARNING) << "Failed to access " << entry_path; - return false; - } - return true; - } - - CHECK(handle_); - ZipEntry64 img_entry; - return FindEntry(handle_, name, &img_entry) == 0; -} - -bool TargetFile::ReadEntryToString(const std::string_view name, std::string* content) const { - if (extracted_input_) { - std::string entry_path = path_ + "/" + std::string(name); - return android::base::ReadFileToString(entry_path, content); - } - - CHECK(handle_); - ZipEntry64 entry; - if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) { - LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err); - return false; - } - - if (entry.uncompressed_length == 0) { - content->clear(); - return true; - } - - if (entry.uncompressed_length > std::numeric_limits::max()) { - LOG(ERROR) << "Failed to extract " << name - << " because's uncompressed size exceeds size of address space. " - << entry.uncompressed_length; - return false; - } - - content->resize(entry.uncompressed_length); - if (auto extract_err = ExtractToMemory( - handle_, &entry, reinterpret_cast(&content->at(0)), entry.uncompressed_length); - extract_err != 0) { - LOG(ERROR) << "failed to read " << name << " from package: " << ErrorCodeString(extract_err); - return false; - } - - return true; -} - -bool TargetFile::ExtractEntryToTempFile(const std::string_view name, - TemporaryFile* temp_file) const { - if (extracted_input_) { - std::string entry_path = path_ + "/" + std::string(name); - return std::filesystem::copy_file(entry_path, temp_file->path, - std::filesystem::copy_options::overwrite_existing); - } - - CHECK(handle_); - ZipEntry64 entry; - if (auto find_err = FindEntry(handle_, name, &entry); find_err != 0) { - LOG(ERROR) << "failed to find " << name << " in the package: " << ErrorCodeString(find_err); - return false; - } - - if (auto status = ExtractEntryToFile(handle_, &entry, temp_file->fd); status != 0) { - LOG(ERROR) << "Failed to extract zip entry " << name << " : " << ErrorCodeString(status); - return false; - } - return true; -} - -bool TargetFile::Open() { - if (!extracted_input_) { - if (auto ret = OpenArchive(path_.c_str(), &handle_); ret != 0) { - LOG(ERROR) << "failed to open source target file " << path_ << ": " << ErrorCodeString(ret); - return false; - } - } - - // Parse the misc info. - std::string misc_info_content; - if (!ReadEntryToString("META/misc_info.txt", &misc_info_content)) { - return false; - } - if (!ParsePropertyFile(misc_info_content, &misc_info_)) { - return false; - } - - return true; -} - -bool TargetFile::GetBuildProps(std::map>* props_map) const { - props_map->clear(); - // Parse the source zip to mock the system props and block devices. We try all the possible - // locations for build props. - constexpr std::string_view kPropLocations[] = { - "SYSTEM/build.prop", - "VENDOR/build.prop", - "PRODUCT/build.prop", - "SYSTEM_EXT/build.prop", - "SYSTEM/vendor/build.prop", - "SYSTEM/product/build.prop", - "SYSTEM/system_ext/build.prop", - "ODM/build.prop", // legacy - "ODM/etc/build.prop", - "VENDOR/odm/build.prop", // legacy - "VENDOR/odm/etc/build.prop", - }; - for (const auto& name : kPropLocations) { - std::string build_prop_content; - if (!ReadEntryToString(name, &build_prop_content)) { - continue; - } - std::map> props; - if (!ParsePropertyFile(build_prop_content, &props)) { - LOG(ERROR) << "Failed to parse build prop in " << name; - return false; - } - for (const auto& [key, value] : props) { - if (auto it = props_map->find(key); it != props_map->end() && it->second != value) { - LOG(WARNING) << "Property " << key << " has different values in property files, we got " - << it->second << " and " << value; - } - props_map->emplace(key, value); - } - } - - return true; -} - -bool TargetFile::ExtractImage(const std::string_view entry_name, const FstabInfo& fstab_info, - const std::string_view work_dir, TemporaryFile* image_file) const { - if (!EntryExists(entry_name)) { - return false; - } - - // We don't need extra work for 'emmc'; use the image file as the block device. - if (fstab_info.fs_type == "emmc" || misc_info_.find("extfs_sparse_flag") == misc_info_.end()) { - if (!ExtractEntryToTempFile(entry_name, image_file)) { - return false; - } - } else { // treated as ext4 sparse image - TemporaryFile sparse_image{ std::string(work_dir) }; - if (!ExtractEntryToTempFile(entry_name, &sparse_image)) { - return false; - } - - // Convert the sparse image to raw. - if (!SimgToImg(sparse_image.fd, image_file->fd)) { - LOG(ERROR) << "Failed to convert " << fstab_info.mount_point << " to raw."; - return false; - } - } - - return true; -} - -bool TargetFile::ParseFstabInfo(std::vector* fstab_info_list) const { - // Parse the fstab file and extract the image files. The location of the fstab actually depends - // on some flags e.g. "no_recovery", "recovery_as_boot". Here we just try all possibilities. - constexpr std::string_view kRecoveryFstabLocations[] = { - "RECOVERY/RAMDISK/system/etc/recovery.fstab", - "RECOVERY/RAMDISK/etc/recovery.fstab", - "BOOT/RAMDISK/system/etc/recovery.fstab", - "BOOT/RAMDISK/etc/recovery.fstab", - }; - std::string fstab_content; - for (const auto& name : kRecoveryFstabLocations) { - if (std::string content; ReadEntryToString(name, &content)) { - fstab_content = std::move(content); - break; - } - } - if (fstab_content.empty()) { - LOG(ERROR) << "Failed to parse the recovery fstab file"; - return false; - } - - // Extract the images and convert them to raw. - if (!ParseFstab(fstab_content, fstab_info_list)) { - LOG(ERROR) << "Failed to mount the block devices for source build."; - return false; - } - - return true; -} diff --git a/updater/update_simulator_main.cpp b/updater/update_simulator_main.cpp deleted file mode 100644 index 6c6989ba..00000000 --- a/updater/update_simulator_main.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include "edify/expr.h" -#include "otautil/error_code.h" -#include "otautil/paths.h" -#include "updater/blockimg.h" -#include "updater/build_info.h" -#include "updater/dynamic_partitions.h" -#include "updater/install.h" -#include "updater/simulator_runtime.h" -#include "updater/updater.h" - -using namespace std::string_literals; - -void Usage(std::string_view name) { - LOG(INFO) << "Usage: " << name << "[--oem_settings ]" - << "[--skip_functions ]" - << " --source " - << " --ota_package "; -} - -Value* SimulatorPlaceHolderFn(const char* name, State* /* state */, - const std::vector>& /* argv */) { - LOG(INFO) << "Skip function " << name << " in host simulation"; - return StringValue("t"); -} - -int main(int argc, char** argv) { - // Write the logs to stdout. - android::base::InitLogging(argv, &android::base::StderrLogger); - - std::string oem_settings; - std::string skip_function_file; - std::string source_target_file; - std::string package_name; - std::string work_dir; - bool keep_images = false; - - constexpr struct option OPTIONS[] = { - { "keep_images", no_argument, nullptr, 0 }, - { "oem_settings", required_argument, nullptr, 0 }, - { "ota_package", required_argument, nullptr, 0 }, - { "skip_functions", required_argument, nullptr, 0 }, - { "source", required_argument, nullptr, 0 }, - { "work_dir", required_argument, nullptr, 0 }, - { nullptr, 0, nullptr, 0 }, - }; - - int arg; - int option_index; - while ((arg = getopt_long(argc, argv, "", OPTIONS, &option_index)) != -1) { - if (arg != 0) { - LOG(ERROR) << "Invalid command argument"; - Usage(argv[0]); - return EXIT_FAILURE; - } - auto option_name = OPTIONS[option_index].name; - // The same oem property file used during OTA generation. It's needed for file_getprop() to - // return the correct value for the source build. - if (option_name == "oem_settings"s) { - oem_settings = optarg; - } else if (option_name == "skip_functions"s) { - skip_function_file = optarg; - } else if (option_name == "source"s) { - source_target_file = optarg; - } else if (option_name == "ota_package"s) { - package_name = optarg; - } else if (option_name == "keep_images"s) { - keep_images = true; - } else if (option_name == "work_dir"s) { - work_dir = optarg; - } else { - Usage(argv[0]); - return EXIT_FAILURE; - } - } - - if (source_target_file.empty() || package_name.empty()) { - Usage(argv[0]); - return EXIT_FAILURE; - } - - // Configure edify's functions. - RegisterBuiltins(); - RegisterInstallFunctions(); - RegisterBlockImageFunctions(); - RegisterDynamicPartitionsFunctions(); - - if (!skip_function_file.empty()) { - std::string content; - if (!android::base::ReadFileToString(skip_function_file, &content)) { - PLOG(ERROR) << "Failed to read " << skip_function_file; - return EXIT_FAILURE; - } - - auto lines = android::base::Split(content, "\n"); - for (const auto& line : lines) { - if (line.empty() || android::base::StartsWith(line, "#")) { - continue; - } - RegisterFunction(line, SimulatorPlaceHolderFn); - } - } - - TemporaryFile temp_saved_source; - TemporaryFile temp_last_command; - TemporaryDir temp_stash_base; - - Paths::Get().set_cache_temp_source(temp_saved_source.path); - Paths::Get().set_last_command_file(temp_last_command.path); - Paths::Get().set_stash_directory_base(temp_stash_base.path); - - TemporaryFile cmd_pipe; - TemporaryDir source_temp_dir; - if (work_dir.empty()) { - work_dir = source_temp_dir.path; - } - - BuildInfo source_build_info(work_dir, keep_images); - if (!source_build_info.ParseTargetFile(source_target_file, false)) { - LOG(ERROR) << "Failed to parse the target file " << source_target_file; - return EXIT_FAILURE; - } - - if (!oem_settings.empty()) { - CHECK_EQ(0, access(oem_settings.c_str(), R_OK)); - source_build_info.SetOemSettings(oem_settings); - } - - Updater updater(std::make_unique(&source_build_info)); - if (!updater.Init(cmd_pipe.release(), package_name, false)) { - return EXIT_FAILURE; - } - - if (!updater.RunUpdate()) { - return EXIT_FAILURE; - } - - LOG(INFO) << "\nscript succeeded, result: " << updater.GetResult(); - - return 0; -} diff --git a/updater/updater.cpp b/updater/updater.cpp deleted file mode 100644 index c5267346..00000000 --- a/updater/updater.cpp +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "updater/updater.h" - -#include -#include - -#include - -#include -#include - -#include "edify/updater_runtime_interface.h" - -Updater::~Updater() { - if (package_handle_) { - CloseArchive(package_handle_); - } -} - -bool Updater::Init(int fd, const std::string_view package_filename, bool is_retry) { - // Set up the pipe for sending commands back to the parent process. - cmd_pipe_.reset(fdopen(fd, "wb")); - if (!cmd_pipe_) { - LOG(ERROR) << "Failed to open the command pipe"; - return false; - } - - setlinebuf(cmd_pipe_.get()); - - if (!mapped_package_.MapFile(std::string(package_filename))) { - LOG(ERROR) << "failed to map package " << package_filename; - return false; - } - if (int open_err = OpenArchiveFromMemory(mapped_package_.addr, mapped_package_.length, - std::string(package_filename).c_str(), &package_handle_); - open_err != 0) { - LOG(ERROR) << "failed to open package " << package_filename << ": " - << ErrorCodeString(open_err); - return false; - } - if (!ReadEntryToString(package_handle_, SCRIPT_NAME, &updater_script_)) { - return false; - } - - is_retry_ = is_retry; - - return true; -} - -bool Updater::RunUpdate() { - CHECK(runtime_); - - // Parse the script. - std::unique_ptr root; - int error_count = 0; - int error = ParseString(updater_script_, &root, &error_count); - if (error != 0 || error_count > 0) { - LOG(ERROR) << error_count << " parse errors"; - return false; - } - - // Evaluate the parsed script. - State state(updater_script_, this); - state.is_retry = is_retry_; - - bool status = Evaluate(&state, root, &result_); - if (status) { - fprintf(cmd_pipe_.get(), "ui_print script succeeded: result was [%s]\n", result_.c_str()); - // Even though the script doesn't abort, still log the cause code if result is empty. - if (result_.empty() && state.cause_code != kNoCause) { - fprintf(cmd_pipe_.get(), "log cause: %d\n", state.cause_code); - } - for (const auto& func : skipped_functions_) { - LOG(WARNING) << "Skipped executing function " << func; - } - return true; - } - - ParseAndReportErrorCode(&state); - return false; -} - -void Updater::WriteToCommandPipe(const std::string_view message, bool flush) const { - fprintf(cmd_pipe_.get(), "%s\n", std::string(message).c_str()); - if (flush) { - fflush(cmd_pipe_.get()); - } -} - -void Updater::UiPrint(const std::string_view message) const { - // "line1\nline2\n" will be split into 3 tokens: "line1", "line2" and "". - // so skip sending empty strings to ui. - std::vector lines = android::base::Split(std::string(message), "\n"); - for (const auto& line : lines) { - if (!line.empty()) { - fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str()); - } - } - - // on the updater side, we need to dump the contents to stderr (which has - // been redirected to the log file). because the recovery will only print - // the contents to screen when processing pipe command ui_print. - LOG(INFO) << message; -} - -std::string Updater::FindBlockDeviceName(const std::string_view name) const { - return runtime_->FindBlockDeviceName(name); -} - -void Updater::ParseAndReportErrorCode(State* state) { - CHECK(state); - if (state->errmsg.empty()) { - LOG(ERROR) << "script aborted (no error message)"; - fprintf(cmd_pipe_.get(), "ui_print script aborted (no error message)\n"); - } else { - LOG(ERROR) << "script aborted: " << state->errmsg; - const std::vector lines = android::base::Split(state->errmsg, "\n"); - for (const std::string& line : lines) { - // Parse the error code in abort message. - // Example: "E30: This package is for bullhead devices." - if (!line.empty() && line[0] == 'E') { - if (sscanf(line.c_str(), "E%d: ", &state->error_code) != 1) { - LOG(ERROR) << "Failed to parse error code: [" << line << "]"; - } - } - fprintf(cmd_pipe_.get(), "ui_print %s\n", line.c_str()); - } - } - - // Installation has been aborted. Set the error code to kScriptExecutionFailure unless - // a more specific code has been set in errmsg. - if (state->error_code == kNoError) { - state->error_code = kScriptExecutionFailure; - } - fprintf(cmd_pipe_.get(), "log error: %d\n", state->error_code); - // Cause code should provide additional information about the abort. - if (state->cause_code != kNoCause) { - fprintf(cmd_pipe_.get(), "log cause: %d\n", state->cause_code); - if (state->cause_code == kPatchApplicationFailure) { - LOG(INFO) << "Patch application failed, retry update."; - fprintf(cmd_pipe_.get(), "retry_update\n"); - } else if (state->cause_code == kEioFailure) { - LOG(INFO) << "Update failed due to EIO, retry update."; - fprintf(cmd_pipe_.get(), "retry_update\n"); - } - } -} - -bool Updater::ReadEntryToString(ZipArchiveHandle za, const std::string& entry_name, - std::string* content) { - ZipEntry64 entry; - int find_err = FindEntry(za, entry_name, &entry); - if (find_err != 0) { - LOG(ERROR) << "failed to find " << entry_name - << " in the package: " << ErrorCodeString(find_err); - return false; - } - if (entry.uncompressed_length > std::numeric_limits::max()) { - LOG(ERROR) << "Failed to extract " << entry_name - << " because's uncompressed size exceeds size of address space. " - << entry.uncompressed_length; - return false; - } - content->resize(entry.uncompressed_length); - int extract_err = ExtractToMemory(za, &entry, reinterpret_cast(&content->at(0)), - entry.uncompressed_length); - if (extract_err != 0) { - LOG(ERROR) << "failed to read " << entry_name - << " from package: " << ErrorCodeString(extract_err); - return false; - } - - return true; -} diff --git a/updater/updater_main.cpp b/updater/updater_main.cpp deleted file mode 100644 index 33d5b5b4..00000000 --- a/updater/updater_main.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include "edify/expr.h" -#include "updater/blockimg.h" -#include "updater/dynamic_partitions.h" -#include "updater/install.h" -#include "updater/updater.h" -#include "updater/updater_runtime.h" - -// Generated by the makefile, this function defines the -// RegisterDeviceExtensions() function, which calls all the -// registration functions for device-specific extensions. -#include "register.inc" - -static void UpdaterLogger(android::base::LogId /* id */, android::base::LogSeverity /* severity */, - const char* /* tag */, const char* /* file */, unsigned int /* line */, - const char* message) { - fprintf(stdout, "%s\n", message); -} - -int main(int argc, char** argv) { - // Various things log information to stdout or stderr more or less - // at random (though we've tried to standardize on stdout). The - // log file makes more sense if buffering is turned off so things - // appear in the right order. - setbuf(stdout, nullptr); - setbuf(stderr, nullptr); - - // We don't have logcat yet under recovery. Update logs will always be written to stdout - // (which is redirected to recovery.log). - android::base::InitLogging(argv, &UpdaterLogger); - - // Run the libcrypto KAT(known answer tests) based self tests. - if (BORINGSSL_self_test() != 1) { - LOG(ERROR) << "Failed to run the boringssl self tests"; - return EXIT_FAILURE; - } - - if (argc != 4 && argc != 5) { - LOG(ERROR) << "unexpected number of arguments: " << argc; - return EXIT_FAILURE; - } - - char* version = argv[1]; - if ((version[0] != '1' && version[0] != '2' && version[0] != '3') || version[1] != '\0') { - // We support version 1, 2, or 3. - LOG(ERROR) << "wrong updater binary API; expected 1, 2, or 3; got " << argv[1]; - return EXIT_FAILURE; - } - - int fd; - if (!android::base::ParseInt(argv[2], &fd)) { - LOG(ERROR) << "Failed to parse fd in " << argv[2]; - return EXIT_FAILURE; - } - - std::string package_name = argv[3]; - - bool is_retry = false; - if (argc == 5) { - if (strcmp(argv[4], "retry") == 0) { - is_retry = true; - } else { - LOG(ERROR) << "unexpected argument: " << argv[4]; - return EXIT_FAILURE; - } - } - - // Configure edify's functions. - RegisterBuiltins(); - RegisterInstallFunctions(); - RegisterBlockImageFunctions(); - RegisterDynamicPartitionsFunctions(); - RegisterDeviceExtensions(); - - auto sehandle = selinux_android_file_context_handle(); - selinux_android_set_sehandle(sehandle); - - Updater updater(std::make_unique(sehandle)); - if (!updater.Init(fd, package_name, is_retry)) { - return EXIT_FAILURE; - } - - if (!updater.RunUpdate()) { - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/updater/updater_runtime.cpp b/updater/updater_runtime.cpp deleted file mode 100644 index bac078cf..00000000 --- a/updater/updater_runtime.cpp +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "updater/updater_runtime.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mounts.h" -#include "otautil/sysutil.h" - -std::string UpdaterRuntime::GetProperty(const std::string_view key, - const std::string_view default_value) const { - return android::base::GetProperty(std::string(key), std::string(default_value)); -} - -std::string UpdaterRuntime::FindBlockDeviceName(const std::string_view name) const { - return std::string(name); -} - -static bool setMountFlag(const std::string& flag, unsigned* mount_flags) { - static constexpr std::pair mount_flags_list[] = { - { "noatime", MS_NOATIME }, - { "noexec", MS_NOEXEC }, - { "nosuid", MS_NOSUID }, - { "nodev", MS_NODEV }, - { "nodiratime", MS_NODIRATIME }, - { "ro", MS_RDONLY }, - { "rw", 0 }, - { "remount", MS_REMOUNT }, - { "bind", MS_BIND }, - { "rec", MS_REC }, - { "unbindable", MS_UNBINDABLE }, - { "private", MS_PRIVATE }, - { "slave", MS_SLAVE }, - { "shared", MS_SHARED }, - { "defaults", 0 }, - }; - - for (const auto& [name, value] : mount_flags_list) { - if (flag == name) { - *mount_flags |= value; - return true; - } - } - return false; -} - -static bool parseMountFlags(const std::string& flags, unsigned* mount_flags, - std::string* fs_options) { - bool is_flag_set = false; - std::vector flag_list; - for (const auto& flag : android::base::Split(flags, ",")) { - if (!setMountFlag(flag, mount_flags)) { - // Unknown flag, so it must be a filesystem specific option. - flag_list.push_back(flag); - } else { - is_flag_set = true; - } - } - *fs_options = android::base::Join(flag_list, ','); - return is_flag_set; -} - -int UpdaterRuntime::Mount(const std::string_view location, const std::string_view mount_point, - const std::string_view fs_type, const std::string_view mount_options) { - std::string mount_point_string(mount_point); - std::string mount_options_string(mount_options); - char* secontext = nullptr; - unsigned mount_flags = 0; - std::string fs_options; - - if (sehandle_) { - selabel_lookup(sehandle_, &secontext, mount_point_string.c_str(), 0755); - setfscreatecon(secontext); - } - - mkdir(mount_point_string.c_str(), 0755); - - if (secontext) { - freecon(secontext); - setfscreatecon(nullptr); - } - - if (!parseMountFlags(mount_options_string, &mount_flags, &fs_options)) { - // Fall back to default - mount_flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME; - } - - return mount(std::string(location).c_str(), mount_point_string.c_str(), - std::string(fs_type).c_str(), mount_flags, fs_options.c_str()); -} - -bool UpdaterRuntime::IsMounted(const std::string_view mount_point) const { - scan_mounted_volumes(); - MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str()); - return vol != nullptr; -} - -std::pair UpdaterRuntime::Unmount(const std::string_view mount_point) { - scan_mounted_volumes(); - MountedVolume* vol = find_mounted_volume_by_mount_point(std::string(mount_point).c_str()); - if (vol == nullptr) { - return { false, -1 }; - } - - int ret = unmount_mounted_volume(vol); - return { true, ret }; -} - -bool UpdaterRuntime::ReadFileToString(const std::string_view filename, std::string* content) const { - return android::base::ReadFileToString(std::string(filename), content); -} - -bool UpdaterRuntime::WriteStringToFile(const std::string_view content, - const std::string_view filename) const { - return android::base::WriteStringToFile(std::string(content), std::string(filename)); -} - -int UpdaterRuntime::WipeBlockDevice(const std::string_view filename, size_t len) const { - android::base::unique_fd fd(open(std::string(filename).c_str(), O_WRONLY)); - if (fd == -1) { - PLOG(ERROR) << "Failed to open " << filename; - return false; - } - // The wipe_block_device function in ext4_utils returns 0 on success and 1 for failure. - return wipe_block_device(fd, len); -} - -int UpdaterRuntime::RunProgram(const std::vector& args, bool is_vfork) const { - CHECK(!args.empty()); - auto argv = StringVectorToNullTerminatedArray(args); - LOG(INFO) << "about to run program [" << args[0] << "] with " << argv.size() << " args"; - - pid_t child = is_vfork ? vfork() : fork(); - if (child == 0) { - execv(argv[0], argv.data()); - PLOG(ERROR) << "run_program: execv failed"; - _exit(EXIT_FAILURE); - } - - int status; - waitpid(child, &status, 0); - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) != 0) { - LOG(ERROR) << "run_program: child exited with status " << WEXITSTATUS(status); - } - } else if (WIFSIGNALED(status)) { - LOG(ERROR) << "run_program: child terminated by signal " << WTERMSIG(status); - } - - return status; -} - -int UpdaterRuntime::Tune2Fs(const std::vector& args) const { - auto tune2fs_args = StringVectorToNullTerminatedArray(args); - // tune2fs changes the filesystem parameters on an ext2 filesystem; it returns 0 on success. - return tune2fs_main(tune2fs_args.size() - 1, tune2fs_args.data()); -} - -std::string UpdaterRuntime::AddSlotSuffix(const std::string_view arg) const { - return std::string(arg) + fs_mgr_get_slot_suffix(); -} diff --git a/updater/updater_runtime_dynamic_partitions.cpp b/updater/updater_runtime_dynamic_partitions.cpp deleted file mode 100644 index 6570cfff..00000000 --- a/updater/updater_runtime_dynamic_partitions.cpp +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "updater/updater_runtime.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -using android::dm::DeviceMapper; -using android::dm::DmDeviceState; -using android::fs_mgr::CreateLogicalPartition; -using android::fs_mgr::CreateLogicalPartitionParams; -using android::fs_mgr::DestroyLogicalPartition; -using android::fs_mgr::LpMetadata; -using android::fs_mgr::MetadataBuilder; -using android::fs_mgr::Partition; -using android::fs_mgr::PartitionOpener; -using android::fs_mgr::SlotNumberForSlotSuffix; - -static constexpr std::chrono::milliseconds kMapTimeout{ 1000 }; - -static std::string GetSuperDevice() { - return "/dev/block/by-name/" + fs_mgr_get_super_partition_name(); -} - -static std::string AddSlotSuffix(const std::string& partition_name) { - return partition_name + fs_mgr_get_slot_suffix(); -} - -static bool UnmapPartitionWithSuffixOnDeviceMapper(const std::string& partition_name_suffix) { - auto state = DeviceMapper::Instance().GetState(partition_name_suffix); - if (state == DmDeviceState::INVALID) { - return true; - } - if (state == DmDeviceState::ACTIVE) { - return DestroyLogicalPartition(partition_name_suffix); - } - LOG(ERROR) << "Unknown device mapper state: " - << static_cast>(state); - return false; -} - -bool UpdaterRuntime::MapPartitionOnDeviceMapper(const std::string& partition_name, - std::string* path) { - auto partition_name_suffix = AddSlotSuffix(partition_name); - auto state = DeviceMapper::Instance().GetState(partition_name_suffix); - if (state == DmDeviceState::INVALID) { - CreateLogicalPartitionParams params = { - .block_device = GetSuperDevice(), - // If device supports A/B, apply non-A/B update to the partition at current slot. Otherwise, - // SlotNumberForSlotSuffix("") returns 0. - .metadata_slot = SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix()), - // If device supports A/B, apply non-A/B update to the partition at current slot. Otherwise, - // fs_mgr_get_slot_suffix() returns empty string. - .partition_name = partition_name_suffix, - .force_writable = true, - .timeout_ms = kMapTimeout, - }; - return CreateLogicalPartition(params, path); - } - - if (state == DmDeviceState::ACTIVE) { - return DeviceMapper::Instance().GetDmDevicePathByName(partition_name_suffix, path); - } - LOG(ERROR) << "Unknown device mapper state: " - << static_cast>(state); - return false; -} - -bool UpdaterRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) { - return ::UnmapPartitionWithSuffixOnDeviceMapper(AddSlotSuffix(partition_name)); -} - -namespace { // Ops - -struct OpParameters { - std::vector tokens; - MetadataBuilder* builder; - - bool ExpectArgSize(size_t size) const { - CHECK(!tokens.empty()); - auto actual = tokens.size() - 1; - if (actual != size) { - LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual; - return false; - } - return true; - } - const std::string& op() const { - CHECK(!tokens.empty()); - return tokens[0]; - } - const std::string& arg(size_t pos) const { - CHECK_LE(pos + 1, tokens.size()); - return tokens[pos + 1]; - } - std::optional uint_arg(size_t pos, const std::string& name) const { - auto str = arg(pos); - uint64_t ret; - if (!android::base::ParseUint(str, &ret)) { - LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str; - return std::nullopt; - } - return ret; - } -}; - -using OpFunction = std::function; -using OpMap = std::map; - -bool PerformOpResize(const OpParameters& params) { - if (!params.ExpectArgSize(2)) return false; - const auto& partition_name_suffix = AddSlotSuffix(params.arg(0)); - auto size = params.uint_arg(1, "size"); - if (!size.has_value()) return false; - - auto partition = params.builder->FindPartition(partition_name_suffix); - if (partition == nullptr) { - LOG(ERROR) << "Failed to find partition " << partition_name_suffix - << " in dynamic partition metadata."; - return false; - } - if (!UnmapPartitionWithSuffixOnDeviceMapper(partition_name_suffix)) { - LOG(ERROR) << "Cannot unmap " << partition_name_suffix << " before resizing."; - return false; - } - if (!params.builder->ResizePartition(partition, size.value())) { - LOG(ERROR) << "Failed to resize partition " << partition_name_suffix << " to size " << *size - << "."; - return false; - } - return true; -} - -bool PerformOpRemove(const OpParameters& params) { - if (!params.ExpectArgSize(1)) return false; - const auto& partition_name_suffix = AddSlotSuffix(params.arg(0)); - - if (!UnmapPartitionWithSuffixOnDeviceMapper(partition_name_suffix)) { - LOG(ERROR) << "Cannot unmap " << partition_name_suffix << " before removing."; - return false; - } - params.builder->RemovePartition(partition_name_suffix); - return true; -} - -bool PerformOpAdd(const OpParameters& params) { - if (!params.ExpectArgSize(2)) return false; - const auto& partition_name_suffix = AddSlotSuffix(params.arg(0)); - const auto& group_name_suffix = AddSlotSuffix(params.arg(1)); - - if (params.builder->AddPartition(partition_name_suffix, group_name_suffix, - LP_PARTITION_ATTR_READONLY) == nullptr) { - LOG(ERROR) << "Failed to add partition " << partition_name_suffix << " to group " - << group_name_suffix << "."; - return false; - } - return true; -} - -bool PerformOpMove(const OpParameters& params) { - if (!params.ExpectArgSize(2)) return false; - const auto& partition_name_suffix = AddSlotSuffix(params.arg(0)); - const auto& new_group_name_suffix = AddSlotSuffix(params.arg(1)); - - auto partition = params.builder->FindPartition(partition_name_suffix); - if (partition == nullptr) { - LOG(ERROR) << "Cannot move partition " << partition_name_suffix << " to group " - << new_group_name_suffix << " because it is not found."; - return false; - } - - auto old_group_name_suffix = partition->group_name(); - if (old_group_name_suffix != new_group_name_suffix) { - if (!params.builder->ChangePartitionGroup(partition, new_group_name_suffix)) { - LOG(ERROR) << "Cannot move partition " << partition_name_suffix << " from group " - << old_group_name_suffix << " to group " << new_group_name_suffix << "."; - return false; - } - } - return true; -} - -bool PerformOpAddGroup(const OpParameters& params) { - if (!params.ExpectArgSize(2)) return false; - const auto& group_name_suffix = AddSlotSuffix(params.arg(0)); - auto maximum_size = params.uint_arg(1, "maximum_size"); - if (!maximum_size.has_value()) return false; - - auto group = params.builder->FindGroup(group_name_suffix); - if (group != nullptr) { - LOG(ERROR) << "Cannot add group " << group_name_suffix << " because it already exists."; - return false; - } - - if (maximum_size.value() == 0) { - LOG(WARNING) << "Adding group " << group_name_suffix << " with no size limits."; - } - - if (!params.builder->AddGroup(group_name_suffix, maximum_size.value())) { - LOG(ERROR) << "Failed to add group " << group_name_suffix << " with maximum size " - << maximum_size.value() << "."; - return false; - } - return true; -} - -bool PerformOpResizeGroup(const OpParameters& params) { - if (!params.ExpectArgSize(2)) return false; - const auto& group_name_suffix = AddSlotSuffix(params.arg(0)); - auto new_size = params.uint_arg(1, "maximum_size"); - if (!new_size.has_value()) return false; - - auto group = params.builder->FindGroup(group_name_suffix); - if (group == nullptr) { - LOG(ERROR) << "Cannot resize group " << group_name_suffix << " because it is not found."; - return false; - } - - auto old_size = group->maximum_size(); - if (old_size != new_size.value()) { - if (!params.builder->ChangeGroupSize(group_name_suffix, new_size.value())) { - LOG(ERROR) << "Cannot resize group " << group_name_suffix << " from " << old_size << " to " - << new_size.value() << "."; - return false; - } - } - return true; -} - -std::vector ListPartitionNamesInGroup(MetadataBuilder* builder, - const std::string& group_name_suffix) { - auto partitions = builder->ListPartitionsInGroup(group_name_suffix); - std::vector partition_names; - std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names), - [](Partition* partition) { return partition->name(); }); - return partition_names; -} - -bool PerformOpRemoveGroup(const OpParameters& params) { - if (!params.ExpectArgSize(1)) return false; - const auto& group_name_suffix = AddSlotSuffix(params.arg(0)); - - auto partition_names = ListPartitionNamesInGroup(params.builder, group_name_suffix); - if (!partition_names.empty()) { - LOG(ERROR) << "Cannot remove group " << group_name_suffix - << " because it still contains partitions [" - << android::base::Join(partition_names, ", ") << "]"; - return false; - } - params.builder->RemoveGroupAndPartitions(group_name_suffix); - return true; -} - -bool PerformOpRemoveAllGroups(const OpParameters& params) { - if (!params.ExpectArgSize(0)) return false; - - auto group_names = params.builder->ListGroups(); - for (const auto& group_name_suffix : group_names) { - auto partition_names = ListPartitionNamesInGroup(params.builder, group_name_suffix); - for (const auto& partition_name_suffix : partition_names) { - if (!UnmapPartitionWithSuffixOnDeviceMapper(partition_name_suffix)) { - LOG(ERROR) << "Cannot unmap " << partition_name_suffix << " before removing group " - << group_name_suffix << "."; - return false; - } - } - params.builder->RemoveGroupAndPartitions(group_name_suffix); - } - return true; -} - -} // namespace - -bool UpdaterRuntime::UpdateDynamicPartitions(const std::string_view op_list_value) { - auto super_device = GetSuperDevice(); - auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0); - if (builder == nullptr) { - LOG(ERROR) << "Failed to load dynamic partition metadata."; - return false; - } - - static const OpMap op_map{ - // clang-format off - {"resize", PerformOpResize}, - {"remove", PerformOpRemove}, - {"add", PerformOpAdd}, - {"move", PerformOpMove}, - {"add_group", PerformOpAddGroup}, - {"resize_group", PerformOpResizeGroup}, - {"remove_group", PerformOpRemoveGroup}, - {"remove_all_groups", PerformOpRemoveAllGroups}, - // clang-format on - }; - - std::vector lines = android::base::Split(std::string(op_list_value), "\n"); - for (const auto& line : lines) { - auto comment_idx = line.find('#'); - auto op_and_args = comment_idx == std::string::npos ? line : line.substr(0, comment_idx); - op_and_args = android::base::Trim(op_and_args); - if (op_and_args.empty()) continue; - - auto tokens = android::base::Split(op_and_args, " "); - const auto& op = tokens[0]; - auto it = op_map.find(op); - if (it == op_map.end()) { - LOG(ERROR) << "Unknown operation in op_list: " << op; - return false; - } - OpParameters params; - params.tokens = tokens; - params.builder = builder.get(); - if (!it->second(params)) { - return false; - } - } - - auto metadata = builder->Export(); - if (metadata == nullptr) { - LOG(ERROR) << "Failed to export metadata."; - return false; - } - - if (!UpdatePartitionTable(super_device, *metadata, 0)) { - LOG(ERROR) << "Failed to write metadata."; - return false; - } - - return true; -}