From 8ff84d7cfb1d51221f61ad6ebd97434b6f8b658a Mon Sep 17 00:00:00 2001 From: Yifan Hong Date: Wed, 19 Dec 2018 16:21:55 -0800 Subject: updater: add functions to modify dynamic partition metadata Test: sideload full OTA on cuttlefish Test: sideload incremental OTA on cuttlefish (that grows system, shrinks vendor, and move vendor to group foo) Test: verify that /cache/recovery/cc46ebfd04058569d0c6c1431c6af6c1328458e4 exists (sha1sum of "system") Bug: 111801737 Change-Id: Ibdf6565bc1b60f3665c01739b4c95a85f0261ae5 --- updater/Android.bp | 3 + updater/Android.mk | 2 + updater/blockimg.cpp | 61 +++- updater/dynamic_partitions.cpp | 435 +++++++++++++++++++++++++++ updater/include/private/utils.h | 21 ++ updater/include/updater/dynamic_partitions.h | 19 ++ updater/updater.cpp | 2 + 7 files changed, 534 insertions(+), 9 deletions(-) create mode 100644 updater/dynamic_partitions.cpp create mode 100644 updater/include/private/utils.h create mode 100644 updater/include/updater/dynamic_partitions.h diff --git a/updater/Android.bp b/updater/Android.bp index c95ec5e90..b80cdb3a0 100644 --- a/updater/Android.bp +++ b/updater/Android.bp @@ -26,12 +26,14 @@ cc_defaults { "libedify", "libotautil", "libext4_utils", + "libdm", "libfec", "libfec_rs", "libverity_tree", "libfs_mgr", "libgtest_prod", "liblog", + "liblp", "libselinux", "libsparse", "libsquashfs_utils", @@ -66,6 +68,7 @@ cc_library_static { srcs: [ "blockimg.cpp", "commands.cpp", + "dynamic_partitions.cpp", "install.cpp", ], diff --git a/updater/Android.mk b/updater/Android.mk index 78e32ba39..c7a6ba989 100644 --- a/updater/Android.mk +++ b/updater/Android.mk @@ -29,12 +29,14 @@ updater_common_static_libraries := \ libedify \ libotautil \ libext4_utils \ + libdm \ libfec \ libfec_rs \ libverity_tree \ libfs_mgr \ libgtest_prod \ liblog \ + liblp \ libselinux \ libsparse \ libsquashfs_utils \ diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index e35d48368..6e5d5bb55 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -70,6 +70,7 @@ static constexpr size_t BLOCKSIZE = 4096; static constexpr mode_t STASH_DIRECTORY_MODE = 0700; static constexpr mode_t STASH_FILE_MODE = 0600; +static constexpr mode_t MARKER_DIRECTORY_MODE = 0700; static CauseCode failure_type = kNoCause; static bool is_retry = false; @@ -167,15 +168,22 @@ static bool UpdateLastCommandIndex(size_t command_index, const std::string& comm return true; } -static bool SetPartitionUpdatedMarker(const std::string& marker) { +bool SetUpdatedMarker(const std::string& marker) { + auto dirname = android::base::Dirname(marker); + auto res = mkdir(dirname.c_str(), MARKER_DIRECTORY_MODE); + if (res == -1 && errno != EEXIST) { + PLOG(ERROR) << "Failed to create directory for marker: " << dirname; + return false; + } + if (!android::base::WriteStringToFile("", marker)) { PLOG(ERROR) << "Failed to write to marker file " << marker; return false; } - if (!FsyncDir(android::base::Dirname(marker))) { + if (!FsyncDir(dirname)) { return false; } - LOG(INFO) << "Wrote partition updated marker to " << marker; + LOG(INFO) << "Wrote updated marker to " << marker; return true; } @@ -1573,6 +1581,43 @@ using CommandFunction = std::function; using CommandMap = std::unordered_map; +static bool Sha1DevicePath(const std::string& path, uint8_t digest[SHA_DIGEST_LENGTH]) { + auto device_name = android::base::Basename(path); + auto dm_target_name_path = "/sys/block/" + device_name + "/dm/name"; + + struct stat sb; + if (stat(dm_target_name_path.c_str(), &sb) == 0) { + // This is a device mapper target. Use partition name as part of the hash instead. Do not + // include extents as part of the hash, because the size of a partition may be shrunk after + // the patches are applied. + std::string dm_target_name; + if (!android::base::ReadFileToString(dm_target_name_path, &dm_target_name)) { + PLOG(ERROR) << "Cannot read " << dm_target_name_path; + return false; + } + SHA1(reinterpret_cast(dm_target_name.data()), dm_target_name.size(), digest); + return true; + } + + if (errno != ENOENT) { + // This is a device mapper target, but its name cannot be retrieved. + PLOG(ERROR) << "Cannot get dm target name for " << path; + return false; + } + + // This doesn't appear to be a device mapper target, but if its name starts with dm-, something + // else might have gone wrong. + if (android::base::StartsWith(device_name, "dm-")) { + LOG(WARNING) << "Device " << path << " starts with dm- but is not mapped by device-mapper."; + } + + // Stash directory should be different for each partition to avoid conflicts when updating + // multiple partitions at the same time, so we use the hash of the block device name as the base + // directory. + SHA1(reinterpret_cast(path.data()), path.size(), digest); + return true; +} + static Value* PerformBlockImageUpdate(const char* name, State* state, const std::vector>& argv, const CommandMap& command_map, bool dryrun) { @@ -1657,12 +1702,10 @@ static Value* PerformBlockImageUpdate(const char* name, State* state, return StringValue(""); } - // Stash directory should be different for each partition to avoid conflicts when updating - // multiple partitions at the same time, so we use the hash of the block device name as the base - // directory. uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast(blockdev_filename->data.data()), - blockdev_filename->data.size(), digest); + if (!Sha1DevicePath(blockdev_filename->data, digest)) { + return StringValue(""); + } params.stashbase = print_sha1(digest); // Possibly do return early on retry, by checking the marker. If the update on this partition has @@ -1884,7 +1927,7 @@ pbiudone: // Create a marker on /cache partition, which allows skipping the update on this partition on // retry. The marker will be removed once booting into normal boot, or before starting next // fresh install. - if (!SetPartitionUpdatedMarker(updated_marker)) { + if (!SetUpdatedMarker(updated_marker)) { LOG(WARNING) << "Failed to set updated marker; continuing"; } } diff --git a/updater/dynamic_partitions.cpp b/updater/dynamic_partitions.cpp new file mode 100644 index 000000000..b50dd75f9 --- /dev/null +++ b/updater/dynamic_partitions.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "updater/dynamic_partitions.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "edify/expr.h" +#include "otautil/error_code.h" +#include "otautil/paths.h" +#include "private/utils.h" + +using android::base::ParseUint; +using android::dm::DeviceMapper; +using android::dm::DmDeviceState; +using android::fs_mgr::CreateLogicalPartition; +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; + +static constexpr std::chrono::milliseconds kMapTimeout{ 1000 }; +static constexpr char kMetadataUpdatedMarker[] = "/dynamic_partition_metadata.UPDATED"; + +static std::string GetSuperDevice() { + return "/dev/block/by-name/" + fs_mgr_get_super_partition_name(); +} + +static std::vector ReadStringArgs(const char* name, State* state, + const std::vector>& argv, + const std::vector& arg_names) { + if (argv.size() != arg_names.size()) { + ErrorAbort(state, kArgsParsingFailure, "%s expects %zu arguments, got %zu", name, + arg_names.size(), argv.size()); + return {}; + } + + std::vector> args; + if (!ReadValueArgs(state, argv, &args)) { + return {}; + } + + CHECK_EQ(args.size(), arg_names.size()); + + for (size_t i = 0; i < arg_names.size(); ++i) { + if (args[i]->type != Value::Type::STRING) { + ErrorAbort(state, kArgsParsingFailure, "%s argument to %s must be string", + arg_names[i].c_str(), name); + return {}; + } + } + + std::vector ret; + std::transform(args.begin(), args.end(), std::back_inserter(ret), + [](const auto& arg) { return arg->data; }); + return ret; +} + +static bool UnmapPartitionOnDeviceMapper(const std::string& partition_name) { + auto state = DeviceMapper::Instance().GetState(partition_name); + if (state == DmDeviceState::INVALID) { + return true; + } + if (state == DmDeviceState::ACTIVE) { + return DestroyLogicalPartition(partition_name, kMapTimeout); + } + LOG(ERROR) << "Unknown device mapper state: " + << static_cast>(state); + return false; +} + +static bool MapPartitionOnDeviceMapper(const std::string& partition_name, std::string* path) { + auto state = DeviceMapper::Instance().GetState(partition_name); + if (state == DmDeviceState::INVALID) { + return CreateLogicalPartition(GetSuperDevice(), 0 /* metadata slot */, partition_name, + true /* force writable */, kMapTimeout, path); + } + + if (state == DmDeviceState::ACTIVE) { + return DeviceMapper::Instance().GetDmDevicePathByName(partition_name, path); + } + LOG(ERROR) << "Unknown device mapper state: " + << static_cast>(state); + return false; +} + +Value* UnmapPartitionFn(const char* name, State* state, + const std::vector>& argv) { + auto args = ReadStringArgs(name, state, argv, { "name" }); + if (args.empty()) return StringValue(""); + + return UnmapPartitionOnDeviceMapper(args[0]) ? StringValue("t") : StringValue(""); +} + +Value* MapPartitionFn(const char* name, State* state, + const std::vector>& argv) { + auto args = ReadStringArgs(name, state, argv, { "name" }); + if (args.empty()) return StringValue(""); + + std::string path; + bool result = MapPartitionOnDeviceMapper(args[0], &path); + return result ? StringValue(path) : StringValue(""); +} + +namespace { // Ops + +struct OpParameters { + std::vector tokens; + MetadataBuilder* builder; + + bool ExpectArgSize(size_t size) const { + CHECK(!tokens.empty()); + auto actual = tokens.size() - 1; + if (actual != size) { + LOG(ERROR) << "Op " << op() << " expects " << size << " args, got " << actual; + return false; + } + return true; + } + const std::string& op() const { + CHECK(!tokens.empty()); + return tokens[0]; + } + const std::string& arg(size_t pos) const { + CHECK_LE(pos + 1, tokens.size()); + return tokens[pos + 1]; + } + std::optional uint_arg(size_t pos, const std::string& name) const { + auto str = arg(pos); + uint64_t ret; + if (!ParseUint(str, &ret)) { + LOG(ERROR) << "Op " << op() << " expects uint64 for argument " << name << ", got " << str; + return std::nullopt; + } + return ret; + } +}; + +using OpFunction = std::function; +using OpMap = std::map; + +bool PerformOpResize(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& partition_name = params.arg(0); + auto size = params.uint_arg(1, "size"); + if (!size.has_value()) return false; + + auto partition = params.builder->FindPartition(partition_name); + if (partition == nullptr) { + LOG(ERROR) << "Failed to find partition " << partition_name + << " in dynamic partition metadata."; + return false; + } + if (!UnmapPartitionOnDeviceMapper(partition_name)) { + LOG(ERROR) << "Cannot unmap " << partition_name << " before resizing."; + return false; + } + if (!params.builder->ResizePartition(partition, size.value())) { + LOG(ERROR) << "Failed to resize partition " << partition_name << " to size " << *size << "."; + return false; + } + return true; +} + +bool PerformOpRemove(const OpParameters& params) { + if (!params.ExpectArgSize(1)) return false; + const auto& partition_name = params.arg(0); + + if (!UnmapPartitionOnDeviceMapper(partition_name)) { + LOG(ERROR) << "Cannot unmap " << partition_name << " before removing."; + return false; + } + params.builder->RemovePartition(partition_name); + return true; +} + +bool PerformOpAdd(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& partition_name = params.arg(0); + const auto& group_name = params.arg(1); + + if (params.builder->AddPartition(partition_name, group_name, LP_PARTITION_ATTR_READONLY) == + nullptr) { + LOG(ERROR) << "Failed to add partition " << partition_name << " to group " << group_name << "."; + return false; + } + return true; +} + +bool PerformOpMove(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& partition_name = params.arg(0); + const auto& new_group = params.arg(1); + + auto partition = params.builder->FindPartition(partition_name); + if (partition == nullptr) { + LOG(ERROR) << "Cannot move partition " << partition_name << " to group " << new_group + << " because it is not found."; + return false; + } + + auto old_group = partition->group_name(); + if (old_group != new_group) { + if (!params.builder->ChangePartitionGroup(partition, new_group)) { + LOG(ERROR) << "Cannot move partition " << partition_name << " from group " << old_group + << " to group " << new_group << "."; + return false; + } + } + return true; +} + +bool PerformOpAddGroup(const OpParameters& params) { + if (!params.ExpectArgSize(2)) return false; + const auto& group_name = 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); + if (group != nullptr) { + LOG(ERROR) << "Cannot add group " << group_name << " because it already exists."; + return false; + } + + if (maximum_size.value() == 0) { + LOG(WARNING) << "Adding group " << group_name << " with no size limits."; + } + + if (!params.builder->AddGroup(group_name, maximum_size.value())) { + LOG(ERROR) << "Failed to add group " << group_name << " 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 = 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); + if (group == nullptr) { + LOG(ERROR) << "Cannot resize group " << group_name << " 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, new_size.value())) { + LOG(ERROR) << "Cannot resize group " << group_name << " from " << old_size << " to " + << new_size.value() << "."; + return false; + } + } + return true; +} + +std::vector ListPartitionNamesInGroup(MetadataBuilder* builder, + const std::string& group_name) { + auto partitions = builder->ListPartitionsInGroup(group_name); + std::vector partition_names; + std::transform(partitions.begin(), partitions.end(), std::back_inserter(partition_names), + [](Partition* partition) { return partition->name(); }); + return partition_names; +} + +bool PerformOpRemoveGroup(const OpParameters& params) { + if (!params.ExpectArgSize(1)) return false; + const auto& group_name = params.arg(0); + + auto partition_names = ListPartitionNamesInGroup(params.builder, group_name); + if (!partition_names.empty()) { + LOG(ERROR) << "Cannot remove group " << group_name << " because it still contains partitions [" + << android::base::Join(partition_names, ", ") << "]"; + return false; + } + params.builder->RemoveGroupAndPartitions(group_name); + return true; +} + +bool PerformOpRemoveAllGroups(const OpParameters& params) { + if (!params.ExpectArgSize(0)) return false; + + auto group_names = params.builder->ListGroups(); + for (const auto& group_name : group_names) { + auto partition_names = ListPartitionNamesInGroup(params.builder, group_name); + for (const auto& partition_name : partition_names) { + if (!UnmapPartitionOnDeviceMapper(partition_name)) { + LOG(ERROR) << "Cannot unmap " << partition_name << " before removing group " << group_name + << "."; + return false; + } + } + params.builder->RemoveGroupAndPartitions(group_name); + } + return true; +} + +} // namespace + +Value* UpdateDynamicPartitionsFn(const char* name, State* state, + const std::vector>& argv) { + if (argv.size() != 1) { + ErrorAbort(state, kArgsParsingFailure, "%s expects 1 arguments, got %zu", name, argv.size()); + return StringValue(""); + } + std::vector> args; + if (!ReadValueArgs(state, argv, &args)) { + return nullptr; + } + const std::unique_ptr& op_list_value = args[0]; + if (op_list_value->type != Value::Type::BLOB) { + ErrorAbort(state, kArgsParsingFailure, "op_list argument to %s must be blob", name); + return StringValue(""); + } + + std::string updated_marker = Paths::Get().stash_directory_base() + kMetadataUpdatedMarker; + if (state->is_retry) { + struct stat sb; + int result = stat(updated_marker.c_str(), &sb); + if (result == 0) { + LOG(INFO) << "Skipping already updated dynamic partition metadata based on marker"; + return StringValue("t"); + } + } else { + // Delete the obsolete marker if any. + std::string err; + if (!android::base::RemoveFileIfExists(updated_marker, &err)) { + LOG(ERROR) << "Failed to remove dynamic partition metadata updated marker " << updated_marker + << ": " << err; + return StringValue(""); + } + } + + auto super_device = GetSuperDevice(); + auto builder = MetadataBuilder::New(PartitionOpener(), super_device, 0); + if (builder == nullptr) { + LOG(ERROR) << "Failed to load dynamic partition metadata."; + return StringValue(""); + } + + static const OpMap op_map{ + // clang-format off + {"resize", PerformOpResize}, + {"remove", PerformOpRemove}, + {"add", PerformOpAdd}, + {"move", PerformOpMove}, + {"add_group", PerformOpAddGroup}, + {"resize_group", PerformOpResizeGroup}, + {"remove_group", PerformOpRemoveGroup}, + {"remove_all_groups", PerformOpRemoveAllGroups}, + // clang-format on + }; + + std::vector lines = android::base::Split(op_list_value->data, "\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 StringValue(""); + } + OpParameters params; + params.tokens = tokens; + params.builder = builder.get(); + if (!it->second(params)) { + return StringValue(""); + } + } + + auto metadata = builder->Export(); + if (metadata == nullptr) { + LOG(ERROR) << "Failed to export metadata."; + return StringValue(""); + } + + if (!UpdatePartitionTable(super_device, *metadata, 0)) { + LOG(ERROR) << "Failed to write metadata."; + return StringValue(""); + } + + if (!SetUpdatedMarker(updated_marker)) { + LOG(ERROR) << "Failed to set metadata updated marker."; + return StringValue(""); + } + + return StringValue("t"); +} + +void RegisterDynamicPartitionsFunctions() { + RegisterFunction("unmap_partition", UnmapPartitionFn); + RegisterFunction("map_partition", MapPartitionFn); + RegisterFunction("update_dynamic_partitions", UpdateDynamicPartitionsFn); +} diff --git a/updater/include/private/utils.h b/updater/include/private/utils.h new file mode 100644 index 000000000..33cf6155d --- /dev/null +++ b/updater/include/private/utils.h @@ -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 + +bool SetUpdatedMarker(const std::string& marker); diff --git a/updater/include/updater/dynamic_partitions.h b/updater/include/updater/dynamic_partitions.h new file mode 100644 index 000000000..31cf859c6 --- /dev/null +++ b/updater/include/updater/dynamic_partitions.h @@ -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(); diff --git a/updater/updater.cpp b/updater/updater.cpp index e87c57a5c..7b5a3f938 100644 --- a/updater/updater.cpp +++ b/updater/updater.cpp @@ -35,6 +35,7 @@ #include "otautil/error_code.h" #include "otautil/sysutil.h" #include "updater/blockimg.h" +#include "updater/dynamic_partitions.h" #include "updater/install.h" // Generated by the makefile, this function defines the @@ -125,6 +126,7 @@ int main(int argc, char** argv) { RegisterBuiltins(); RegisterInstallFunctions(); RegisterBlockImageFunctions(); + RegisterDynamicPartitionsFunctions(); RegisterDeviceExtensions(); // Parse the script. -- cgit v1.2.3