Restore applypatch/updater/edify for non-A/B OTA support
Google deleted this source from bootable/recovery outright (commit c7ebad5f, "rm -rf non-AB code", 2024-04-05) — confirmed present at android-14, absent at android-16.0.0_r4. The runtime side (install.cpp's SetUpNonAbUpdateCommands) and build-side packaging (ota_from_target_files.py's GenerateNonAbOtaPackage) both survived; only the source that builds the update-binary executable itself was removed. Sourced from TeamWin's android_bootable_recovery (android-14.1), the only living copy found, with install/ZipUtil.cpp, get_args.cpp, and set_metadata.cpp intentionally left out (only wired into TeamWin's modified install.cpp, which PawletOS isn't adopting — twrpinstall carries its own independent copies). Kept as its own repo, not folded into a recovery fork, so it's buildable independent of whichever recovery UI ends up in use.
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
// 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 {
|
||||
default_applicable_licenses: ["external_update_binary_updater_license"],
|
||||
}
|
||||
|
||||
// external_update_binary is a standalone repo (not nested inside
|
||||
// bootable/recovery), so this declares its own license module instead of
|
||||
// depending on bootable/recovery's internal "bootable_recovery_license" name.
|
||||
license {
|
||||
name: "external_update_binary_updater_license",
|
||||
visibility: [":__subpackages__"],
|
||||
license_kinds: [
|
||||
"SPDX-license-identifier-Apache-2.0",
|
||||
],
|
||||
license_text: [
|
||||
"//external/update_binary:NOTICE",
|
||||
],
|
||||
}
|
||||
|
||||
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",
|
||||
"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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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)
|
||||
|
||||
ifneq ($(wildcard external/e2fsprogs/misc/tune2fs.h),)
|
||||
tune2fs_static_libraries := \
|
||||
libext2_com_err \
|
||||
libext2_blkid \
|
||||
libext2_quota \
|
||||
libext2_uuid \
|
||||
libext2_e2p \
|
||||
libext2fs
|
||||
LOCAL_CFLAGS += -DHAVE_LIBTUNE2FS
|
||||
else
|
||||
tune2fs_static_libraries :=
|
||||
endif
|
||||
|
||||
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 \
|
||||
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
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* 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_;
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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);
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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_;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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();
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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();
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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_;
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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_;
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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_;
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 };
|
||||
};
|
||||
+1392
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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);
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user