rm -rf non-AB code

Bug: 324360816
Test: th
Change-Id: I3d82d9031446be355d8a1d077ab83283c7cc769c
This commit is contained in:
Kelvin Zhang 2024-04-05 09:48:10 -07:00
parent bd27912393
commit c7ebad5fd6
64 changed files with 12 additions and 15700 deletions

View file

@ -35,7 +35,10 @@ package {
// See: http://go/android-license-faq // See: http://go/android-license-faq
license { license {
name: "bootable_recovery_license", name: "bootable_recovery_license",
visibility: [":__subpackages__"], visibility: [
":__subpackages__",
"//bootable/deprecated-ota:__subpackages__",
],
license_kinds: [ license_kinds: [
"SPDX-license-identifier-Apache-2.0", "SPDX-license-identifier-Apache-2.0",
"SPDX-license-identifier-MIT", "SPDX-license-identifier-MIT",

View file

@ -84,5 +84,3 @@ endif
include $(BUILD_PHONY_PACKAGE) include $(BUILD_PHONY_PACKAGE)
include \
$(LOCAL_PATH)/updater/Android.mk \

View file

@ -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",
],
}

View file

@ -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.

View file

@ -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 <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <openssl/sha.h>
#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<unsigned char>(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<unsigned char> 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<const uint8_t*>(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<const uint8_t*>(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<std::string> 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 "<invalid-partition>";
}
std::ostream& operator<<(std::ostream& os, const Partition& partition) {
os << partition.ToString();
return os;
}

View file

@ -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 <android-base/logging.h>
// See the comments for applypatch() function.
int main(int argc, char** argv) {
android::base::InitLogging(argv);
return applypatch_modes(argc, argv);
}

View file

@ -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 <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <memory>
#include <string>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <openssl/sha.h>
#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<Value> 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>(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:<target-file>:<target-size>:<target-sha1>\n\n"
"flash mode\n"
" applypatch --flash <source-file>\n"
" --target EMMC:<target-file>:<target-size>:<target-sha1>\n\n"
"patch mode\n"
" applypatch [--bonus <bonus-file>]\n"
" --patch <patch-file>\n"
" --target EMMC:<target-file>:<target-size>:<target-sha1>\n"
" --source EMMC:<source-file>:<source-size>:<source-sha1>\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;
}

View file

@ -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

View file

@ -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 <stdio.h>
#include <sys/types.h>
#include <string>
#include <android-base/logging.h>
#include <bsdiff/bspatch.h>
#include <openssl/sha.h>
#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<const uint8_t*>(&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<const uint8_t*>(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;
}

View file

@ -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 <dirent.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <unistd.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <set>
#include <string>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include "applypatch/applypatch.h"
#include "otautil/paths.h"
static int EliminateOpenFiles(const std::string& dirname, std::set<std::string>* files) {
std::unique_ptr<DIR, decltype(&closedir)> 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<DIR, decltype(&closedir)> 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<std::string> FindExpendableFiles(
const std::string& dirname, const std::function<bool(const std::string&)>& name_filter) {
std::unique_ptr<DIR, decltype(&closedir)> 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<std::string> 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<std::string>(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<unsigned int>::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<int64_t>(sf.f_bsize);
auto free_space = sf.f_bsize * sf.f_bavail;
if (f_bsize == 0 || free_space / f_bsize != static_cast<int64_t>(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<std::string> 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<int64_t(const std::string&)>& space_checker) {
// The requested size cannot exceed max int64_t.
if (static_cast<uint64_t>(bytes_needed) >
static_cast<uint64_t>(std::numeric_limits<int64_t>::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<int64_t>(bytes_needed)) {
return true;
}
std::vector<std::string> 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<int64_t>(bytes_needed)) {
return true;
}
}
return false;
}

File diff suppressed because it is too large Load diff

View file

@ -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<const char**>(argv));
}

View file

@ -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 <applypatch/imgpatch.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/cdefs.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory>
#include <string>
#include <vector>
#include <android-base/logging.h>
#include <android-base/memory.h>
#include <applypatch/applypatch.h>
#include <applypatch/imgdiff.h>
#include <openssl/sha.h>
#include <zlib.h>
#include "edify/expr.h"
#include "otautil/print_sha1.h"
static inline int64_t Read8(const void *address) {
return android::base::get_unaligned<int64_t>(address);
}
static inline int32_t Read4(const void *address) {
return android::base::get_unaligned<int32_t>(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<size_t>(Read8(deflate_header + 32));
CHECK_GT(expected_target_length, static_cast<size_t>(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<uint8_t> 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<const char*>(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<size_t>(Read8(normal_header));
size_t src_len = static_cast<size_t>(Read8(normal_header + 8));
size_t patch_offset = static_cast<size_t>(Read8(normal_header + 16));
if (src_start + src_len > 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<size_t>(Read4(raw_header));
if (pos + data_len > patch.data.size()) {
printf("failed to read chunk %d raw data\n", i);
return -1;
}
if (sink(reinterpret_cast<const unsigned char*>(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<size_t>(Read8(deflate_header));
size_t src_len = static_cast<size_t>(Read8(deflate_header + 8));
size_t patch_offset = static_cast<size_t>(Read8(deflate_header + 16));
size_t expanded_len = static_cast<size_t>(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<unsigned char> 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;
}

View file

@ -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 <stdint.h>
#include <functional>
#include <memory>
#include <ostream>
#include <string>
#include <vector>
#include <openssl/sha.h>
// Forward declaration to avoid including "edify/expr.h" in the header.
struct Value;
struct FileContents {
uint8_t sha1[SHA_DIGEST_LENGTH];
std::vector<unsigned char> data;
};
using SinkFn = std::function<size_t(const unsigned char*, size_t)>;
// 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 "<digest>:<anything>". 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:<device>:<size>:<hash>". 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<int64_t(const std::string&)>& space_checker);
#endif

View file

@ -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 <stddef.h>
// 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

View file

@ -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 <stddef.h>
#include <stdio.h>
#include <sys/types.h>
#include <string>
#include <vector>
#include <bsdiff/bsdiff.h>
#include <ziparchive/zip_archive.h>
#include <zlib.h>
#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<uint8_t>* 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<uint8_t> data);
bool SetBonusData(const std::vector<uint8_t>& bonus_data);
bool operator==(const ImageChunk& other) const;
bool operator!=(const ImageChunk& other) const {
return !(*this == other);
}
/*
* Cause a gzip chunk to be treated as a normal chunk (ie, as a blob of uninterpreted data).
* The resulting patch will likely be about as big as the target file, but it lets us handle
* the case of images where some gzip chunks are reconstructible but others aren't (by treating
* the ones that aren't as normal chunks).
*/
void ChangeDeflateChunkToNormal();
/*
* Verify that we can reproduce exactly the same compressed data that we started with. Sets the
* level, method, windowBits, memLevel, and strategy fields in the chunk to the encoding
* parameters needed to produce the right output.
*/
bool ReconstructDeflateChunk();
bool IsAdjacentNormal(const ImageChunk& other) const;
void MergeAdjacentNormal(const ImageChunk& other);
/*
* Compute a bsdiff patch between |src| and |tgt|; Store the result in the patch_data.
* |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk is used
* repeatedly, pass nullptr if not needed.
*/
static bool MakePatch(const ImageChunk& tgt, const ImageChunk& src,
std::vector<uint8_t>* patch_data,
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<uint8_t>* input_file_ptr_; // ptr to the full content of original input file
size_t raw_data_len_;
// deflate encoder parameters
int compress_level_;
// --- for CHUNK_DEFLATE chunks only: ---
std::vector<uint8_t> uncompressed_data_;
std::string entry_name_; // used for zip entries
};
// PatchChunk stores the patch data between a source chunk and a target chunk. It also keeps track
// of the metadata of src&tgt chunks (e.g. offset, raw data length, uncompressed data length).
class PatchChunk {
public:
PatchChunk(const ImageChunk& tgt, const ImageChunk& src, std::vector<uint8_t> data);
// Construct a CHUNK_RAW patch from the target data directly.
explicit PatchChunk(const ImageChunk& tgt);
// Return true if raw data size is smaller than the patch size.
static bool RawDataIsSmaller(const ImageChunk& tgt, size_t patch_size);
// 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<PatchChunk>& 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<uint8_t> data_; // storage for the patch data
};
// Interface for zip_mode and image_mode images. We initialize the image from an input file and
// split the file content into a list of image chunks.
class Image {
public:
explicit Image(bool is_source) : is_source_(is_source) {}
virtual ~Image() {}
// Create a list of image chunks from input file.
virtual bool Initialize(const std::string& filename) = 0;
// Look for runs of adjacent normal chunks and compress them down into a single chunk. (Such
// runs can be produced when deflate chunks are changed to normal chunks.)
void MergeAdjacentNormalChunks();
void DumpChunks() const;
// Non const iterators to access the stored ImageChunks.
std::vector<ImageChunk>::iterator begin() {
return chunks_.begin();
}
std::vector<ImageChunk>::iterator end() {
return chunks_.end();
}
std::vector<ImageChunk>::const_iterator cbegin() const {
return chunks_.cbegin();
}
std::vector<ImageChunk>::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<uint8_t>* file_content);
bool is_source_; // True if it's for source chunks.
std::vector<ImageChunk> chunks_; // Internal storage of ImageChunk.
std::vector<uint8_t> file_content_; // Store the whole input file in memory.
};
class ZipModeImage : public Image {
public:
explicit ZipModeImage(bool is_source, 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<ImageChunk>& chunks, const std::vector<uint8_t>& 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<ZipModeImage>& split_tgt_images,
const std::vector<ZipModeImage>& split_src_images,
const std::vector<SortedRangeSet>& 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<ZipModeImage>* split_tgt_images,
std::vector<ZipModeImage>* split_src_images,
std::vector<SortedRangeSet>* 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<ZipModeImage>& split_tgt_images,
const std::vector<ZipModeImage>& split_src_images,
std::vector<SortedRangeSet>& 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<ImageChunk>& split_tgt_chunks,
const std::vector<ImageChunk>& split_src_chunks,
std::vector<ZipModeImage>* split_tgt_images,
std::vector<ZipModeImage>* 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<PatchChunk>* 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<uint8_t>& bonus_data);
// In Image Mode, verify that the source and target images have the same chunk structure (ie, the
// same sequence of deflate and normal chunks).
static bool CheckAndProcessChunks(ImageModeImage* tgt_image, ImageModeImage* src_image);
// In image mode, generate patches against the given source chunks and bonus_data; write the
// result to |patch_name|.
static bool GeneratePatches(const ImageModeImage& tgt_image, const ImageModeImage& src_image,
const std::string& patch_name);
};
#endif // _APPLYPATCH_IMGDIFF_IMAGE_H

View file

@ -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 <sys/types.h>
#include <functional>
using SinkFn = std::function<size_t(const unsigned char*, size_t)>;
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data,
size_t patch_size, SinkFn sink);
#endif // _APPLYPATCH_IMGPATCH_H

View file

@ -1,4 +0,0 @@
service vendor_flash_recovery /vendor/bin/install-recovery.sh
class main
oneshot
user root

View file

@ -27,6 +27,7 @@
#include <android-base/file.h> #include <android-base/file.h>
#include <android-base/hex.h> #include <android-base/hex.h>
#include <android-base/logging.h>
#include <android-base/properties.h> #include <android-base/properties.h>
#include <android-base/stringprintf.h> #include <android-base/stringprintf.h>
#include <android-base/unique_fd.h> #include <android-base/unique_fd.h>
@ -175,6 +176,7 @@ bool write_bootloader_message(const bootloader_message& boot, std::string* err)
bool clear_bootloader_message(std::string* err) { bool clear_bootloader_message(std::string* err) {
bootloader_message boot = {}; bootloader_message boot = {};
LOG(INFO) << "Clearing BCB";
return write_bootloader_message(boot, err); return write_bootloader_message(boot, err);
} }
@ -194,11 +196,12 @@ bool write_bootloader_message_to(const std::vector<std::string>& options,
} }
bool update_bootloader_message(const std::vector<std::string>& options, std::string* err) { bool update_bootloader_message(const std::vector<std::string>& options, std::string* err) {
bootloader_message boot; bootloader_message boot{};
if (!read_bootloader_message(&boot, err)) { if (!read_bootloader_message(&boot, err)) {
return false; return false;
} }
update_bootloader_message_in_struct(&boot, options); update_bootloader_message_in_struct(&boot, options);
LOG(INFO) << "Writing BCB " << boot.command << " " << boot.recovery;
return write_bootloader_message(boot, err); 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) { bool write_reboot_bootloader(std::string* err) {
bootloader_message boot; bootloader_message boot{};
if (!read_bootloader_message(&boot, err)) { if (!read_bootloader_message(&boot, err)) {
return false; return false;
} }
@ -233,6 +236,7 @@ bool write_reboot_bootloader(std::string* err) {
return false; return false;
} }
strlcpy(boot.command, "bootonce-bootloader", sizeof(boot.command)); strlcpy(boot.command, "bootonce-bootloader", sizeof(boot.command));
LOG(INFO) << "Writing BCB cmd: " << boot.command << " args: " << boot.recovery;
return write_bootloader_message(boot, err); return write_bootloader_message(boot, err);
} }

View file

@ -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",
],
}

View file

@ -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())

View file

@ -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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#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>& expr, std::string* result) {
if (result == nullptr) {
return false;
}
std::unique_ptr<Value> 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>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& argv) {
std::unique_ptr<Value> left(EvaluateValue(state, argv[0]));
if (!left) {
return nullptr;
}
return EvaluateValue(state, argv[1]);
}
Value* LessThanIntFn(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 2) {
state->errmsg = "less_than_int expects 2 arguments";
return nullptr;
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 2) {
state->errmsg = "greater_than_int expects 2 arguments";
return nullptr;
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
return StringValue(name);
}
// -----------------------------------------------------------------
// the function table
// -----------------------------------------------------------------
static std::unordered_map<std::string, Function> 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<std::unique_ptr<Expr>>& argv,
std::vector<std::string>* args) {
return ReadArgs(state, argv, args, 0, argv.size());
}
bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
std::vector<std::string>* 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<std::unique_ptr<Expr>>& argv,
std::vector<std::unique_ptr<Value>>* args) {
return ReadValueArgs(state, argv, args, 0, argv.size());
}
bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
std::vector<std::unique_ptr<Value>>* 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<Value> 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) {}

View file

@ -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 <unistd.h>
#include <memory>
#include <string>
#include <vector>
#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<std::unique_ptr<Expr>>& argv);
struct Expr {
Function fn;
std::string name;
std::vector<std::unique_ptr<Expr>> 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>& 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>& expr, std::string* result);
// Glue to make an Expr out of a literal.
Value* Literal(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& argv);
Value* LogicalAndFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
Value* LogicalOrFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
Value* LogicalNotFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
Value* SubstringFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
Value* EqualityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
Value* InequalityFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
Value* SequenceFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
// Global builtins, registered by RegisterBuiltins().
Value* IfElseFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
Value* AssertFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& argv);
Value* AbortFn(const char* name, State* state, const std::vector<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& argv,
std::vector<std::string>* args);
bool ReadArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
std::vector<std::string>* 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<std::unique_ptr<Expr>>& argv,
std::vector<std::unique_ptr<Value>>* args);
bool ReadValueArgs(State* state, const std::vector<std::unique_ptr<Expr>>& argv,
std::vector<std::unique_ptr<Value>>* 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<Expr>* root, int* error_count);
#endif // _EXPRESSION_H

View file

@ -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 <stdint.h>
#include <string>
#include <string_view>
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;
};

View file

@ -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 <string>
#include <string_view>
#include <vector>
// 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<bool, int> 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<std::string>& args, bool is_vfork) const = 0;
// Runs tune2fs with arguments |args|.
virtual int Tune2Fs(const std::vector<std::string>& 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;
};

View file

@ -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 <string.h>
#include <string>
#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;
}
<STR>{
\" {
++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<char>(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;

View file

@ -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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory>
#include <string>
#include <vector>
#include <android-base/macros.h>
#include "edify/expr.h"
#include "yydefs.h"
#include "parser.h"
extern int gLine;
extern int gColumn;
void yyerror(std::unique_ptr<Expr>* root, int* error_count, const char* s);
int yyparse(std::unique_ptr<Expr>* 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<std::unique_ptr<Expr>>* args;
}
%token AND OR SUBSTR SUPERSTR EQ NE IF THEN ELSE ENDIF
%token <str> STRING BAD
%type <expr> expr
%type <args> arglist
%destructor { delete $$; } expr
%destructor { delete $$; } arglist
%parse-param {std::unique_ptr<Expr>* 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<std::unique_ptr<Expr>>;
}
| expr {
$$ = new std::vector<std::unique_ptr<Expr>>;
$$->emplace_back($1);
}
| arglist ',' expr {
UNUSED($1);
$$->push_back(std::unique_ptr<Expr>($3));
}
;
%%
void yyerror(std::unique_ptr<Expr>* 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<Expr>* root, int* error_count) {
yy_switch_to_buffer(yy_scan_string(str.c_str()));
return yyparse(root, error_count);
}

View file

@ -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

View file

@ -98,5 +98,6 @@ cc_library_static {
"//bootable/recovery/install", "//bootable/recovery/install",
"//bootable/recovery/minadbd", "//bootable/recovery/minadbd",
"//bootable/recovery/tests", "//bootable/recovery/tests",
"//bootable/deprecated-ota:__subpackages__",
], ],
} }

View file

@ -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 // 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). // have 32-bit android.hardware.health@2.0.so or libbootloader_message.so on marlin).
librecovery_static_libs = [ librecovery_static_libs = [
@ -125,16 +107,10 @@ cc_test {
defaults: [ defaults: [
"recovery_test_defaults", "recovery_test_defaults",
"libupdater_defaults",
"libupdater_device_defaults",
], ],
test_suites: ["device-tests"], test_suites: ["device-tests"],
tidy_timeout_srcs: [
"unit/commands_test.cpp",
],
srcs: [ srcs: [
"unit/*.cpp", "unit/*.cpp",
], ],
@ -143,7 +119,7 @@ cc_test {
"libbinder_ndk", "libbinder_ndk",
], ],
static_libs: libapplypatch_static_libs + librecovery_static_libs + [ static_libs: librecovery_static_libs + [
"android.hardware.health-translate-ndk", "android.hardware.health-translate-ndk",
"android.hardware.health-V3-ndk", "android.hardware.health-V3-ndk",
"libhealthshim", "libhealthshim",
@ -152,8 +128,6 @@ cc_test {
"libminui", "libminui",
"librecovery_utils", "librecovery_utils",
"libotautil", "libotautil",
"libupdater_device",
"libupdater_core",
"libupdate_verifier", "libupdate_verifier",
"libprotobuf-cpp-lite", "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 { cc_fuzz {
name: "libinstall_verify_package_fuzzer", name: "libinstall_verify_package_fuzzer",
defaults: [ defaults: [

View file

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configuration description="Runs recovery_host_test.">
<option name="null-device" value="true" />
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
<option name="force-root" value="false" />
</target_preparer>
<option name="not-shardable" value="true" />
<test class="com.android.tradefed.testtype.HostGTest" >
<option name="module-name" value="recovery_host_test" />
<option name="native-test-timeout" value="5m"/>
</test>
</configuration>

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <bsdiff/bsdiff.h>
#include <gtest/gtest.h>
#include <openssl/sha.h>
#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<const uint8_t*>(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<std::string>& 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<std::string> 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<std::string> 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<std::string> 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<const uint8_t*>(src_content.data()), src_content.size(),
reinterpret_cast<const uint8_t*>(tgt_content.data()), tgt_content.size(),
patch_file.path, nullptr));
std::vector<std::string> 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<std::string> 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<std::string> args{
"applypatch", "--flash", from_testdata_base("recovery.img"), "--target", target,
};
ASSERT_EQ(0, InvokeApplyPatchModes(args));
VerifyPatchedTarget(target);
}
TEST_F(ApplyPatchModesTest, FlashModeInvalidArgs) {
std::vector<std::string> 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" }));
}

View file

@ -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 <dirent.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
#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<std::string> FindFilesInDir(const std::string& dirname) {
std::vector<std::string> file_list;
std::unique_ptr<DIR, decltype(&closedir)> 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<std::string>& 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<std::string> files = FindFilesInDir(dirname);
return PARTITION_SIZE - 4096 * files.size();
}
TemporaryDir mock_cache;
TemporaryDir mock_log_dir;
private:
std::vector<std::string> temporary_files_;
};
TEST_F(FreeCacheTest, FreeCacheSmoke) {
std::vector<std::string> 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<std::string>{ "file3" }, FindFilesInDir(mock_cache.path));
ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_cache.path));
}
TEST_F(FreeCacheTest, FreeCacheFreeSpaceCheckerError) {
std::vector<std::string> 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<std::string> 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<std::string>{ "file1" }, FindFilesInDir(mock_cache.path));
}
TEST_F(FreeCacheTest, FreeCacheLogsSmoke) {
std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> expected = { "block.map", "command", "last_install" };
ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
}

View file

@ -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 <algorithm>
#include <string>
#include <android-base/strings.h>
#include <gtest/gtest.h>
#include <openssl/sha.h>
#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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string>(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<std::string> 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<uint8_t>* block_buffer) -> int {
std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a');
return 0;
};
auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
std::vector<uint8_t> 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<uint8_t>* block_buffer) -> int {
std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a');
return 0;
};
auto stash_reader = [](const std::string&, std::vector<uint8_t>* stash_buffer) -> int {
std::fill_n(stash_buffer->begin(), kBlockSize, 'b');
return 0;
};
std::vector<uint8_t> 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<uint8_t>*) -> int { return 0; };
auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
std::vector<uint8_t> 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<uint8_t> buffer(source.blocks() * kBlockSize);
auto failing_block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return -1; };
auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, failing_block_reader, stash_reader));
auto block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return 0; };
auto failing_stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return -1; };
ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, failing_stash_reader));
}
TEST(TransferListTest, Parse) {
std::vector<std::string> 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<bool>(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<std::string> 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<bool>(transfer_list));
}
TEST(TransferListTest, Parse_ZeroTotalBlocks) {
std::vector<std::string> 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<bool>(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());
}

View file

@ -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 <memory>
#include <string>
#include <gtest/gtest.h>
#include "edify/expr.h"
static void expect(const std::string& expr_str, const char* expected) {
std::unique_ptr<Expr> 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> 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);
}

File diff suppressed because it is too large Load diff

View file

@ -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 <stdint.h>
#include <stdio.h>
#include <map>
#include <string>
#include <vector>
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <bsdiff/bsdiff.h>
#include <gtest/gtest.h>
#include <ziparchive/zip_writer.h>
#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<string, string>& 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<const uint8_t*>(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<const uint8_t*>(src.data()), src.size(),
reinterpret_cast<const uint8_t*>(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<SimulatorRuntime>(&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<string> 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"(
#<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
# 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<FstabInfo> fstab_info;
EXPECT_TRUE(target_file.ParseFstabInfo(&fstab_info));
std::vector<std::vector<string>> transformed;
std::transform(fstab_info.begin(), fstab_info.end(), std::back_inserter(transformed),
[](const FstabInfo& info) {
return std::vector<string>{ info.blockdev_name, info.mount_point, info.fs_type };
});
std::vector<std::vector<string>> 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<string, string> 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<string, string> 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<string, string> 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<std::string> 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<string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string> 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<string, string> 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);
}

File diff suppressed because it is too large Load diff

View file

@ -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,
},
},
}

View file

@ -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_<libname>()". 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)

File diff suppressed because it is too large Load diff

View file

@ -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 <stdio.h>
#include <set>
#include <vector>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#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<FstabInfo> 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<std::string, std::less<>> ro_product_props = {
"ro.product.brand", "ro.product.device", "ro.product.manufacturer", "ro.product.model",
"ro.product.name"
};
const std::vector<std::string> 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;
}

View file

@ -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 <stdint.h>
#include <string.h>
#include <functional>
#include <ostream>
#include <string>
#include <vector>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <openssl/sha.h>
#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<std::string>& 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.
//
// <tgt_ranges> <src_block_count> - <[stash_id:location] ...>
// (loads data from stashes only)
//
// <tgt_ranges> <src_block_count> <src_ranges>
// (loads data from source image only)
//
// <tgt_ranges> <src_block_count> <src_ranges> <src_ranges_location> <[stash_id:location] ...>
// (loads data from both of source image and stashes)
// At least it needs to provide three args: <tgt_ranges>, <src_block_count> and "-"/<src_ranges>.
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);
// <src_block_count>
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 <src_ranges> [<src_ranges_location>]
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<StashInfo> 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<std::string> 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<std::string> 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 <rangeset>
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 <stash_id> <src_ranges>
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 <stash_id>
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) {
// <hash>
if (pos + 1 > tokens.size()) {
*err = "missing hash";
return {};
}
std::string hash = tokens[pos++];
if (!ParseTargetInfoAndSourceInfo(
std::vector<std::string>(tokens.cbegin() + pos, tokens.cend()), hash, &target_info,
hash, &source_info, err)) {
return {};
}
} else if (op == Type::BSDIFF || op == Type::IMGDIFF) {
// <offset> <length> <srchash> <dsthash>
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<std::string>(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) {
// <hash_tree_ranges> <source_ranges> <hash_algorithm> <salt_hex> <root_hash>
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<uint8_t>* dest, const RangeSet& locs,
const std::vector<uint8_t>& 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<uint8_t>* buffer, size_t block_size,
const std::function<int(const RangeSet&, std::vector<uint8_t>*)>& block_reader,
const std::function<int(const std::string&, std::vector<uint8_t>*)>& 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<uint8_t> 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<uint8_t>& 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<std::string> 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;
}

View file

@ -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 <sys/stat.h>
#include <sys/types.h>
#include <memory>
#include <string>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#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<std::string> ReadStringArgs(const char* name, State* state,
const std::vector<std::unique_ptr<Expr>>& argv,
const std::vector<std::string>& 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<std::unique_ptr<Value>> 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<std::string> 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
ErrorAbort(state, kArgsParsingFailure, "%s expects 1 arguments, got %zu", name, argv.size());
return StringValue("");
}
std::vector<std::unique_ptr<Value>> args;
if (!ReadValueArgs(state, argv, &args)) {
return nullptr;
}
const std::unique_ptr<Value>& 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);
}

View file

@ -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 <stdint.h>
#include <functional>
#include <ostream>
#include <string>
#include <vector>
#include <gtest/gtest_prod.h> // 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<StashInfo> 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<uint8_t>* buffer, size_t block_size,
const std::function<int(const RangeSet&, std::vector<uint8_t>*)>& block_reader,
const std::function<int(const std::string&, std::vector<uint8_t>*)>& 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<uint8_t>& 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<StashInfo> 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 <tgt_ranges>
// - Fill the indicated blocks with zeros.
// - Meaningful args: TargetInfo
//
// new <tgt_ranges>
// - Fill the blocks with data read from the new_data file.
// - Meaningful args: TargetInfo
//
// erase <tgt_ranges>
// - Mark the given blocks as empty.
// - Meaningful args: TargetInfo
//
// move <hash> <...>
// - Read the source blocks, write result to target blocks.
// - Meaningful args: TargetInfo, SourceInfo
//
// See the note below for <...>.
//
// bsdiff <patchstart> <patchlen> <srchash> <dsthash> <...>
// imgdiff <patchstart> <patchlen> <srchash> <dsthash> <...>
// - 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:
//
// <tgt_ranges> <src_block_count> - <[stash_id:stash_location] ...>
// (loads data from stashes only)
//
// <tgt_ranges> <src_block_count> <src_ranges>
// (loads data from source image only)
//
// <tgt_ranges> <src_block_count> <src_ranges> <src_ranges_location>
// <[stash_id:stash_location] ...>
// (loads data from both of source image and stashes)
//
// stash <stash_id> <src_ranges>
// - Load the given source blocks and stash the data in the given slot of the stash table.
// - Meaningful args: StashInfo
//
// free <stash_id>
// - Free the given stash data.
// - Meaningful args: StashInfo
//
// compute_hash_tree <hash_tree_ranges> <source_ranges> <hash_algorithm> <salt_hex> <root_hash>
// - 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<std::string>& 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<Command>& 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<Command> commands_;
};

View file

@ -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 <string>
bool SetUpdatedMarker(const std::string& marker);

View file

@ -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 <string>
void RegisterBlockImageFunctions();
#endif

View file

@ -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 <list>
#include <map>
#include <string>
#include <string_view>
#include <android-base/file.h>
// 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<std::string, std::string, std::less<>> 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<std::string, FakeBlockDevice, std::less<>> blockdev_map_;
std::list<TemporaryFile> temp_files_;
std::string work_dir_; // A temporary directory to store the extracted image files
bool keep_images_;
};

View file

@ -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();

View file

@ -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();

View file

@ -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 <map>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#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<bool, int> 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<std::string>& args, bool is_vfork) const override;
int Tune2Fs(const std::vector<std::string>& 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<std::string, std::string, std::less<>> mounted_partitions_;
};

View file

@ -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 <map>
#include <string>
#include <string_view>
#include <vector>
#include <android-base/file.h>
#include <ziparchive/zip_archive.h>
// 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<std::string, std::string, std::less<>>* props_map) const;
// Parses the fstab and save the information about each partition to mount into |fstab_info_list|.
bool ParseFstabInfo(std::vector<FstabInfo>* 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<std::string, std::string, std::less<>> misc_info_;
};

View file

@ -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 <stdint.h>
#include <stdio.h>
#include <memory>
#include <string>
#include <string_view>
#include <ziparchive/zip_archive.h>
#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<UpdaterRuntimeInterface> 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<UpdaterRuntimeInterface> runtime_;
MemMapping mapped_package_;
ZipArchiveHandle package_handle_{ nullptr };
std::string updater_script_;
bool is_retry_{ false };
std::unique_ptr<FILE, decltype(&fclose)> cmd_pipe_{ nullptr, fclose };
std::string result_;
std::vector<std::string> skipped_functions_;
};

View file

@ -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 <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#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<bool, int> 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<std::string>& args, bool is_vfork) const override;
int Tune2Fs(const std::vector<std::string>& 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 };
};

View file

@ -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 <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <ftw.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/capability.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <time.h>
#include <unistd.h>
#include <utime.h>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parsedouble.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <applypatch/applypatch.h>
#include <bootloader_message/bootloader_message.h>
#include <ext4_utils/wipe.h>
#include <openssl/sha.h>
#include <selinux/label.h>
#include <selinux/selinux.h>
#include <ziparchive/zip_archive.h>
#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 <cutils/memory.h> // 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<std::unique_ptr<Expr>>& argv) {
std::vector<std::string> args;
if (!ReadArgs(state, argv, &args)) {
return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name);
}
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<std::unique_ptr<Expr>>& 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<std::string> 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<std::string> 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<size_t>::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<uint8_t*>(&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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 2) {
return ErrorAbort(state, kArgsParsingFailure,
"%s(): Invalid number of args (expected 2, got %zu)", name, argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 3) {
return ErrorAbort(state, kArgsParsingFailure,
"%s(): Invalid number of args (expected 3, got %zu)", name, argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Value>> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 4 && argv.size() != 5) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 4-5 args, got %zu", name,
argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
}
std::vector<std::string> 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=<bytes> mount_point=<location>
// fs_type="f2fs" partition_type="EMMC" location=device fs_size=<bytes> mount_point=<location>
// 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 5) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 5 args, got %zu", name,
argv.size());
}
std::vector<std::string> 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<std::string> 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<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 2) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 2) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
argv.size());
}
std::vector<std::string> 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<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 args, got %zu", name,
argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() < 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects at least 1 arg", name);
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 2) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 2) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 2) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 2) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& 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<std::unique_ptr<Expr>>& argv) {
if (argv.empty()) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects args, got %zu", name, argv.size());
}
std::vector<std::string> 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<std::unique_ptr<Expr>>& argv) {
if (argv.size() != 1) {
return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
}
std::vector<std::string> 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);
}

View file

@ -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 <errno.h>
#include <fcntl.h>
#include <mntent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <string>
#include <vector>
#include <android-base/logging.h>
struct MountedVolume {
std::string device;
std::string mount_point;
std::string filesystem;
std::string flags;
};
static std::vector<MountedVolume*> 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;
}

View file

@ -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);

View file

@ -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 <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <unordered_set>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <ext4_utils/wipe.h>
#include <selinux/label.h>
#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<bool, int> 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<std::string>& args, bool /* is_vfork */) const {
LOG(INFO) << "Running program with args " << android::base::Join(args, " ");
return 0;
}
int SimulatorRuntime::Tune2Fs(const std::vector<std::string>& 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<std::string> commands{
"resize", "remove", "add", "move",
"add_group", "resize_group", "remove_group", "remove_all_groups",
};
std::vector<std::string> 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);
}

View file

@ -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 <unistd.h>
#include <algorithm>
#include <filesystem>
#include <memory>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <sparse/sparse.h>
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<sparse_file, decltype(&sparse_file_destroy)> 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<std::string, std::string, std::less<>>* props_map) {
LOG(INFO) << "Start parsing build property\n";
std::vector<std::string> 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<FstabInfo>* fstab_info_list) {
LOG(INFO) << "parsing fstab\n";
std::vector<std::string> lines = android::base::Split(std::string(fstab), "\n");
for (const auto& line : lines) {
if (line.empty() || line[0] == '#') continue;
// <block_device> <mount_point> <fs_type> <mount_flags> optional:<fs_mgr_flags>
std::vector<std::string> 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<size_t>::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<uint8_t*>(&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<std::string, std::string, std::less<>>* 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<std::string, std::string, std::less<>> 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<FstabInfo>* 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;
}

View file

@ -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 <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <string_view>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#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 <oem_property_file>]"
<< "[--skip_functions <skip_function_file>]"
<< " --source <source_target_file>"
<< " --ota_package <ota_package>";
}
Value* SimulatorPlaceHolderFn(const char* name, State* /* state */,
const std::vector<std::unique_ptr<Expr>>& /* 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<SimulatorRuntime>(&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;
}

View file

@ -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 <string.h>
#include <unistd.h>
#include <string>
#include <android-base/logging.h>
#include <android-base/strings.h>
#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<Expr> 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<std::string> 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<std::string> 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<size_t>::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<uint8_t*>(&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;
}

View file

@ -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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <string>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <openssl/crypto.h>
#include <selinux/android.h>
#include <selinux/label.h>
#include <selinux/selinux.h>
#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<UpdaterRuntime>(sehandle));
if (!updater.Init(fd, package_name, is_retry)) {
return EXIT_FAILURE;
}
if (!updater.RunUpdate()) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View file

@ -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 <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <ext4_utils/wipe.h>
#include <fs_mgr.h>
#include <selinux/label.h>
#include <tune2fs.h>
#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<const char*, unsigned> 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<std::string> 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<bool, int> 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<std::string>& 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<std::string>& 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();
}

View file

@ -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 <algorithm>
#include <chrono>
#include <iterator>
#include <optional>
#include <string>
#include <type_traits>
#include <vector>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <fs_mgr.h>
#include <fs_mgr_dm_linear.h>
#include <libdm/dm.h>
#include <liblp/builder.h>
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<std::underlying_type_t<DmDeviceState>>(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<std::underlying_type_t<DmDeviceState>>(state);
return false;
}
bool UpdaterRuntime::UnmapPartitionOnDeviceMapper(const std::string& partition_name) {
return ::UnmapPartitionWithSuffixOnDeviceMapper(AddSlotSuffix(partition_name));
}
namespace { // Ops
struct OpParameters {
std::vector<std::string> 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<uint64_t> 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<bool(const OpParameters&)>;
using OpMap = std::map<std::string, OpFunction>;
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<std::string> ListPartitionNamesInGroup(MetadataBuilder* builder,
const std::string& group_name_suffix) {
auto partitions = builder->ListPartitionsInGroup(group_name_suffix);
std::vector<std::string> 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<std::string> 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;
}