update_engine resize dynamic partitions during OTA.
update_engine uses device mapper to resize dynamic partitions
before opening the devices to apply the update.
* DeltaPerformer calls BootControlInterface::InitPartitionMetadata
when parsing the update manifest. The implementation for
BootControlAndroid::InitPartitionMetadata does the following
if sizes for dynamic partitions are incorrect (assuming updating
from slot A to B):
* Load metadata from metadata slot A
* Delete all extents of partitions at slot B (with _b suffix)
* Add extents for partitions at slot B
* Write metadata to metadata slot B
* Re-map all partitions at slot B using metadata slot B with
force_writable = true
* BootControlAndroid::GetPartitionDevice() checks device-mapper
before returning static partitions.
* PostinstallRunnerAction::Cleanup calls BootControlInterface::Cleanup
which unmaps all partitions at slot B.
A partition "foo" is considered dynamic if foo_a exists as a dynamic
partition OR foo_b does NOT exist as a static partition.
Bug: 110717529
Test: manual ota
Test: update_engine_unittests --gtest_filter=*BootControlAndroid*
Change-Id: I50f410b486a874242663624801c3694151bdda18
diff --git a/Android.mk b/Android.mk
index c49d3c2..062babb 100644
--- a/Android.mk
+++ b/Android.mk
@@ -216,8 +216,10 @@
ue_libupdate_engine_boot_control_exported_shared_libraries := \
libbootloader_message \
+ libfs_mgr \
libhwbinder \
libhidlbase \
+ liblp \
libutils \
[email protected] \
$(ue_update_metadata_protos_exported_shared_libraries)
@@ -238,7 +240,8 @@
$(ue_common_shared_libraries) \
$(ue_libupdate_engine_boot_control_exported_shared_libraries)
LOCAL_SRC_FILES := \
- boot_control_android.cc
+ boot_control_android.cc \
+ dynamic_partition_control_android.cc
include $(BUILD_STATIC_LIBRARY)
ifeq ($(local_use_omaha),1)
@@ -919,6 +922,7 @@
$(ue_common_static_libraries) \
$(ue_libpayload_generator_exported_static_libraries)
LOCAL_SHARED_LIBRARIES := \
+ libhidltransport \
$(ue_common_shared_libraries) \
$(ue_libpayload_generator_exported_shared_libraries)
LOCAL_SRC_FILES := \
@@ -1029,6 +1033,7 @@
LOCAL_SHARED_LIBRARIES += \
$(ue_libupdate_engine_android_exported_shared_libraries)
LOCAL_SRC_FILES += \
+ boot_control_android_unittest.cc \
update_attempter_android_unittest.cc
endif # local_use_omaha == 1
include $(BUILD_NATIVE_TEST)
diff --git a/boot_control_android.cc b/boot_control_android.cc
index 12a3a10..abf898e 100644
--- a/boot_control_android.cc
+++ b/boot_control_android.cc
@@ -20,22 +20,32 @@
#include <utility>
#include <base/bind.h>
-#include <base/files/file_util.h>
#include <base/logging.h>
#include <bootloader_message/bootloader_message.h>
#include <brillo/message_loops/message_loop.h>
#include "update_engine/common/utils.h"
+#include "update_engine/dynamic_partition_control_android.h"
using std::string;
+using android::dm::DmDeviceState;
+using android::fs_mgr::MetadataBuilder;
+using android::fs_mgr::Partition;
+using android::fs_mgr::UpdatePartitionTable;
+using android::hardware::hidl_string;
using android::hardware::Return;
using android::hardware::boot::V1_0::BoolResult;
using android::hardware::boot::V1_0::CommandResult;
using android::hardware::boot::V1_0::IBootControl;
-using android::hardware::hidl_string;
+using Slot = chromeos_update_engine::BootControlInterface::Slot;
+using PartitionSizes =
+ chromeos_update_engine::BootControlInterface::PartitionSizes;
namespace {
+
+constexpr char kZeroGuid[] = "00000000-0000-0000-0000-000000000000";
+
auto StoreResultCallback(CommandResult* dest) {
return [dest](const CommandResult& result) { *dest = result; };
}
@@ -47,7 +57,7 @@
// Factory defined in boot_control.h.
std::unique_ptr<BootControlInterface> CreateBootControl() {
- std::unique_ptr<BootControlAndroid> boot_control(new BootControlAndroid());
+ auto boot_control = std::make_unique<BootControlAndroid>();
if (!boot_control->Init()) {
return nullptr;
}
@@ -65,9 +75,15 @@
LOG(INFO) << "Loaded boot control hidl hal.";
+ dynamic_control_ = std::make_unique<DynamicPartitionControlAndroid>();
+
return true;
}
+void BootControlAndroid::Cleanup() {
+ dynamic_control_->Cleanup();
+}
+
unsigned int BootControlAndroid::GetNumSlots() const {
return module_->getNumberSlots();
}
@@ -76,43 +92,9 @@
return module_->getCurrentSlot();
}
-bool BootControlAndroid::GetPartitionDevice(const string& partition_name,
- Slot slot,
- string* device) const {
- // We can't use fs_mgr to look up |partition_name| because fstab
- // doesn't list every slot partition (it uses the slotselect option
- // to mask the suffix).
- //
- // We can however assume that there's an entry for the /misc mount
- // point and use that to get the device file for the misc
- // partition. This helps us locate the disk that |partition_name|
- // resides on. From there we'll assume that a by-name scheme is used
- // so we can just replace the trailing "misc" by the given
- // |partition_name| and suffix corresponding to |slot|, e.g.
- //
- // /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
- // /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a
- //
- // If needed, it's possible to relax the by-name assumption in the
- // future by trawling /sys/block looking for the appropriate sibling
- // of misc and then finding an entry in /dev matching the sysfs
- // entry.
-
- string err, misc_device = get_bootloader_message_blk_device(&err);
- if (misc_device.empty()) {
- LOG(ERROR) << "Unable to get misc block device: " << err;
- return false;
- }
-
- if (!utils::IsSymlink(misc_device.c_str())) {
- LOG(ERROR) << "Device file " << misc_device << " for /misc "
- << "is not a symlink.";
- return false;
- }
-
- string suffix;
+bool BootControlAndroid::GetSuffix(Slot slot, string* suffix) const {
auto store_suffix_cb = [&suffix](hidl_string cb_suffix) {
- suffix = cb_suffix.c_str();
+ *suffix = cb_suffix.c_str();
};
Return<void> ret = module_->getSuffix(slot, store_suffix_cb);
@@ -121,10 +103,56 @@
<< SlotName(slot);
return false;
}
+ return true;
+}
+
+bool BootControlAndroid::GetPartitionDevice(const string& partition_name,
+ Slot slot,
+ string* device) const {
+ string suffix;
+ if (!GetSuffix(slot, &suffix)) {
+ return false;
+ }
+
+ const string target_partition_name = partition_name + suffix;
+
+ // DeltaPerformer calls InitPartitionMetadata before calling
+ // InstallPlan::LoadPartitionsFromSlots. After InitPartitionMetadata,
+ // the partition must be re-mapped with force_writable == true. Hence,
+ // we only need to check device mapper.
+ if (dynamic_control_->IsDynamicPartitionsEnabled()) {
+ switch (dynamic_control_->GetState(target_partition_name)) {
+ case DmDeviceState::ACTIVE:
+ if (dynamic_control_->GetDmDevicePathByName(target_partition_name,
+ device)) {
+ LOG(INFO) << target_partition_name
+ << " is mapped on device mapper: " << *device;
+ return true;
+ }
+ LOG(ERROR) << target_partition_name
+ << " is mapped but path is unknown.";
+ return false;
+
+ case DmDeviceState::INVALID:
+ // Try static partitions.
+ break;
+
+ case DmDeviceState::SUSPENDED: // fallthrough
+ default:
+ LOG(ERROR) << target_partition_name
+ << " is mapped on device mapper but state is unknown";
+ return false;
+ }
+ }
+
+ string device_dir_str;
+ if (!dynamic_control_->GetDeviceDir(&device_dir_str)) {
+ return false;
+ }
base::FilePath path =
- base::FilePath(misc_device).DirName().Append(partition_name + suffix);
- if (!base::PathExists(path)) {
+ base::FilePath(device_dir_str).Append(target_partition_name);
+ if (!dynamic_control_->DeviceExists(path.value())) {
LOG(ERROR) << "Device file " << path.value() << " does not exist.";
return false;
}
@@ -196,4 +224,250 @@
brillo::MessageLoop::kTaskIdNull;
}
+namespace {
+
+// Resize |partition_name|_|slot| to the given |size|.
+bool ResizePartition(MetadataBuilder* builder,
+ const string& target_partition_name,
+ uint64_t size) {
+ Partition* partition = builder->FindPartition(target_partition_name);
+ if (partition == nullptr) {
+ LOG(ERROR) << "Cannot find " << target_partition_name << " in metadata.";
+ return false;
+ }
+
+ uint64_t old_size = partition->size();
+ const string action = "resize " + target_partition_name + " in super (" +
+ std::to_string(old_size) + " -> " +
+ std::to_string(size) + " bytes)";
+ if (!builder->ResizePartition(partition, size)) {
+ LOG(ERROR) << "Cannot " << action << "; see previous log messages.";
+ return false;
+ }
+
+ if (partition->size() != size) {
+ LOG(ERROR) << "Cannot " << action
+ << "; value is misaligned and partition should have been "
+ << partition->size();
+ return false;
+ }
+
+ LOG(INFO) << "Successfully " << action;
+
+ return true;
+}
+
+bool ResizePartitions(DynamicPartitionControlInterface* dynamic_control,
+ const string& super_device,
+ Slot target_slot,
+ const string& target_suffix,
+ const PartitionSizes& logical_sizes,
+ MetadataBuilder* builder) {
+ // Delete all extents to ensure that each partition has enough space to
+ // grow.
+ for (const auto& pair : logical_sizes) {
+ const string target_partition_name = pair.first + target_suffix;
+ if (builder->FindPartition(target_partition_name) == nullptr) {
+ // Use constant GUID because it is unused.
+ LOG(INFO) << "Adding partition " << target_partition_name << " to slot "
+ << BootControlInterface::SlotName(target_slot) << " in "
+ << super_device;
+ if (builder->AddPartition(target_partition_name,
+ kZeroGuid,
+ LP_PARTITION_ATTR_READONLY) == nullptr) {
+ LOG(ERROR) << "Cannot add partition " << target_partition_name;
+ return false;
+ }
+ }
+ if (!ResizePartition(builder, pair.first + target_suffix, 0 /* size */)) {
+ return false;
+ }
+ }
+
+ for (const auto& pair : logical_sizes) {
+ if (!ResizePartition(builder, pair.first + target_suffix, pair.second)) {
+ LOG(ERROR) << "Not enough space?";
+ return false;
+ }
+ }
+
+ if (!dynamic_control->StoreMetadata(super_device, builder, target_slot)) {
+ return false;
+ }
+ return true;
+}
+
+// Assume upgrading from slot A to B. A partition foo is considered dynamic
+// iff one of the following:
+// 1. foo_a exists as a dynamic partition (so it should continue to be a
+// dynamic partition)
+// 2. foo_b does not exist as a static partition (in which case we may be
+// adding a new partition).
+bool IsDynamicPartition(DynamicPartitionControlInterface* dynamic_control,
+ const base::FilePath& device_dir,
+ MetadataBuilder* source_metadata,
+ const string& partition_name,
+ const string& source_suffix,
+ const string& target_suffix) {
+ bool dynamic_source_exist =
+ source_metadata->FindPartition(partition_name + source_suffix) != nullptr;
+ bool static_target_exist = dynamic_control->DeviceExists(
+ device_dir.Append(partition_name + target_suffix).value());
+
+ return dynamic_source_exist || !static_target_exist;
+}
+
+bool FilterPartitionSizes(DynamicPartitionControlInterface* dynamic_control,
+ const base::FilePath& device_dir,
+ const PartitionSizes& partition_sizes,
+ MetadataBuilder* source_metadata,
+ const string& source_suffix,
+ const string& target_suffix,
+ PartitionSizes* logical_sizes) {
+ for (const auto& pair : partition_sizes) {
+ if (!IsDynamicPartition(dynamic_control,
+ device_dir,
+ source_metadata,
+ pair.first,
+ source_suffix,
+ target_suffix)) {
+ // In the future we can check static partition sizes, but skip for now.
+ LOG(INFO) << pair.first << " is static; assume its size is "
+ << pair.second << " bytes.";
+ continue;
+ }
+
+ logical_sizes->insert(pair);
+ }
+ return true;
+}
+
+// Return false if partition sizes are all correct in metadata slot
+// |target_slot|. If so, no need to resize. |logical_sizes| have format like
+// {vendor: size, ...}, and fail if a partition is not found.
+bool NeedResizePartitions(DynamicPartitionControlInterface* dynamic_control,
+ const string& super_device,
+ Slot target_slot,
+ const string& suffix,
+ const PartitionSizes& logical_sizes) {
+ auto target_metadata =
+ dynamic_control->LoadMetadataBuilder(super_device, target_slot);
+ if (target_metadata == nullptr) {
+ LOG(INFO) << "Metadata slot " << BootControlInterface::SlotName(target_slot)
+ << " in " << super_device
+ << " is corrupted; attempt to recover from source slot.";
+ return true;
+ }
+
+ for (const auto& pair : logical_sizes) {
+ Partition* partition = target_metadata->FindPartition(pair.first + suffix);
+ if (partition == nullptr) {
+ LOG(INFO) << "Cannot find " << pair.first << suffix << " at slot "
+ << BootControlInterface::SlotName(target_slot) << " in "
+ << super_device << ". Need to resize.";
+ return true;
+ }
+ if (partition->size() != pair.second) {
+ LOG(INFO) << super_device << ":"
+ << BootControlInterface::SlotName(target_slot) << ":"
+ << pair.first << suffix << ": size == " << partition->size()
+ << " but requested " << pair.second << ". Need to resize.";
+ return true;
+ }
+ LOG(INFO) << super_device << ":"
+ << BootControlInterface::SlotName(target_slot) << ":"
+ << pair.first << suffix << ": size == " << partition->size()
+ << " as requested.";
+ }
+ LOG(INFO) << "No need to resize at metadata slot "
+ << BootControlInterface::SlotName(target_slot) << " in "
+ << super_device;
+ return false;
+}
+} // namespace
+
+bool BootControlAndroid::InitPartitionMetadata(
+ Slot target_slot, const PartitionSizes& partition_sizes) {
+ if (!dynamic_control_->IsDynamicPartitionsEnabled()) {
+ return true;
+ }
+
+ string device_dir_str;
+ if (!dynamic_control_->GetDeviceDir(&device_dir_str)) {
+ return false;
+ }
+ base::FilePath device_dir(device_dir_str);
+ string super_device = device_dir.Append(LP_METADATA_PARTITION_NAME).value();
+
+ Slot current_slot = GetCurrentSlot();
+ if (target_slot == current_slot) {
+ LOG(ERROR) << "Cannot call InitPartitionMetadata on current slot.";
+ return false;
+ }
+
+ string current_suffix;
+ if (!GetSuffix(current_slot, ¤t_suffix)) {
+ return false;
+ }
+
+ string target_suffix;
+ if (!GetSuffix(target_slot, &target_suffix)) {
+ return false;
+ }
+
+ auto builder =
+ dynamic_control_->LoadMetadataBuilder(super_device, current_slot);
+ if (builder == nullptr) {
+ return false;
+ }
+
+ // Read metadata from current slot to determine which partitions are logical
+ // and may be resized. Do not read from target slot because metadata at
+ // target slot may be corrupted.
+ PartitionSizes logical_sizes;
+ if (!FilterPartitionSizes(dynamic_control_.get(),
+ device_dir,
+ partition_sizes,
+ builder.get() /* source metadata */,
+ current_suffix,
+ target_suffix,
+ &logical_sizes)) {
+ return false;
+ }
+
+ // Read metadata from target slot to determine if the sizes are correct. Only
+ // test logical partitions.
+ if (NeedResizePartitions(dynamic_control_.get(),
+ super_device,
+ target_slot,
+ target_suffix,
+ logical_sizes)) {
+ if (!ResizePartitions(dynamic_control_.get(),
+ super_device,
+ target_slot,
+ target_suffix,
+ logical_sizes,
+ builder.get())) {
+ return false;
+ }
+ }
+
+ // Unmap all partitions, and remap partitions if size is non-zero.
+ for (const auto& pair : logical_sizes) {
+ if (!dynamic_control_->UnmapPartitionOnDeviceMapper(
+ pair.first + target_suffix, true /* wait */)) {
+ return false;
+ }
+ if (pair.second == 0) {
+ continue;
+ }
+ string map_path;
+ if (!dynamic_control_->MapPartitionOnDeviceMapper(
+ super_device, pair.first + target_suffix, target_slot, &map_path)) {
+ return false;
+ }
+ }
+ return true;
+}
+
} // namespace chromeos_update_engine
diff --git a/boot_control_android.h b/boot_control_android.h
index 1de0e41..24ab4dc 100644
--- a/boot_control_android.h
+++ b/boot_control_android.h
@@ -17,11 +17,16 @@
#ifndef UPDATE_ENGINE_BOOT_CONTROL_ANDROID_H_
#define UPDATE_ENGINE_BOOT_CONTROL_ANDROID_H_
+#include <map>
+#include <memory>
#include <string>
#include <android/hardware/boot/1.0/IBootControl.h>
+#include <base/files/file_util.h>
+#include <liblp/builder.h>
#include "update_engine/common/boot_control.h"
+#include "update_engine/dynamic_partition_control_interface.h"
namespace chromeos_update_engine {
@@ -46,9 +51,18 @@
bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+ bool InitPartitionMetadata(Slot slot,
+ const PartitionSizes& partition_sizes) override;
+ void Cleanup() override;
private:
::android::sp<::android::hardware::boot::V1_0::IBootControl> module_;
+ std::unique_ptr<DynamicPartitionControlInterface> dynamic_control_;
+
+ friend class BootControlAndroidTest;
+
+ // Wrapper method of IBootControl::getSuffix().
+ bool GetSuffix(Slot slot, std::string* out) const;
DISALLOW_COPY_AND_ASSIGN(BootControlAndroid);
};
diff --git a/boot_control_android_unittest.cc b/boot_control_android_unittest.cc
new file mode 100644
index 0000000..9744b42
--- /dev/null
+++ b/boot_control_android_unittest.cc
@@ -0,0 +1,670 @@
+//
+// 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 "update_engine/boot_control_android.h"
+
+#include <set>
+
+#include <android-base/strings.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/mock_boot_control_hal.h"
+#include "update_engine/mock_dynamic_partition_control.h"
+
+using android::base::Join;
+using android::fs_mgr::MetadataBuilder;
+using android::hardware::Void;
+using testing::_;
+using testing::AnyNumber;
+using testing::Contains;
+using testing::Eq;
+using testing::Invoke;
+using testing::Key;
+using testing::MakeMatcher;
+using testing::Matcher;
+using testing::MatcherInterface;
+using testing::MatchResultListener;
+using testing::NiceMock;
+using testing::Return;
+
+namespace chromeos_update_engine {
+
+constexpr const uint32_t kMaxNumSlots = 2;
+constexpr const char* kSlotSuffixes[kMaxNumSlots] = {"_a", "_b"};
+constexpr const char* kFakeDevicePath = "/fake/dev/path/";
+constexpr const char* kFakeMappedPath = "/fake/mapped/path/";
+constexpr const uint32_t kFakeMetadataSize = 65536;
+constexpr const char* kZeroGuid = "00000000-0000-0000-0000-000000000000";
+
+// A map describing the size of each partition.
+using PartitionSizes = std::map<std::string, uint64_t>;
+
+// C++ standards do not allow uint64_t (aka unsigned long) to be the parameter
+// of user-defined literal operators.
+unsigned long long operator"" _MiB(unsigned long long x) { // NOLINT
+ return x << 20;
+}
+unsigned long long operator"" _GiB(unsigned long long x) { // NOLINT
+ return x << 30;
+}
+
+template <typename U, typename V>
+std::ostream& operator<<(std::ostream& os, const std::map<U, V>& param) {
+ os << "{";
+ bool first = true;
+ for (const auto& pair : param) {
+ if (!first)
+ os << ", ";
+ os << pair.first << ":" << pair.second;
+ first = false;
+ }
+ return os << "}";
+}
+
+inline std::string GetDevice(const std::string& name) {
+ return kFakeDevicePath + name;
+}
+inline std::string GetSuperDevice() {
+ return GetDevice(LP_METADATA_PARTITION_NAME);
+}
+
+struct TestParam {
+ uint32_t source;
+ uint32_t target;
+};
+std::ostream& operator<<(std::ostream& os, const TestParam& param) {
+ return os << "{source: " << param.source << ", target:" << param.target
+ << "}";
+}
+
+std::unique_ptr<MetadataBuilder> NewFakeMetadata(const PartitionSizes& sizes) {
+ auto builder = MetadataBuilder::New(10_GiB, kFakeMetadataSize, kMaxNumSlots);
+ EXPECT_NE(nullptr, builder);
+ if (builder == nullptr)
+ return nullptr;
+ for (const auto& pair : sizes) {
+ auto p = builder->AddPartition(pair.first, kZeroGuid, 0 /* attr */);
+ EXPECT_TRUE(p && builder->ResizePartition(p, pair.second));
+ }
+ return builder;
+}
+
+class MetadataMatcher : public MatcherInterface<MetadataBuilder*> {
+ public:
+ explicit MetadataMatcher(const PartitionSizes& partition_sizes)
+ : partition_sizes_(partition_sizes) {}
+ bool MatchAndExplain(MetadataBuilder* metadata,
+ MatchResultListener* listener) const override {
+ bool success = true;
+ for (const auto& pair : partition_sizes_) {
+ auto p = metadata->FindPartition(pair.first);
+ if (p == nullptr) {
+ if (success)
+ *listener << "; ";
+ *listener << "No partition " << pair.first;
+ success = false;
+ continue;
+ }
+ if (p->size() != pair.second) {
+ if (success)
+ *listener << "; ";
+ *listener << "Partition " << pair.first << " has size " << p->size()
+ << ", expected " << pair.second;
+ success = false;
+ }
+ }
+ return success;
+ }
+
+ void DescribeTo(std::ostream* os) const override {
+ *os << "expect: " << partition_sizes_;
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "expect not: " << partition_sizes_;
+ }
+
+ private:
+ PartitionSizes partition_sizes_;
+};
+
+inline Matcher<MetadataBuilder*> MetadataMatches(
+ const PartitionSizes& partition_sizes) {
+ return MakeMatcher(new MetadataMatcher(partition_sizes));
+}
+
+class BootControlAndroidTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Fake init bootctl_
+ bootctl_.module_ = new NiceMock<MockBootControlHal>();
+ bootctl_.dynamic_control_ =
+ std::make_unique<NiceMock<MockDynamicPartitionControl>>();
+
+ ON_CALL(module(), getNumberSlots()).WillByDefault(Invoke([] {
+ return kMaxNumSlots;
+ }));
+ ON_CALL(module(), getSuffix(_, _))
+ .WillByDefault(Invoke([](auto slot, auto cb) {
+ EXPECT_LE(slot, kMaxNumSlots);
+ cb(slot < kMaxNumSlots ? kSlotSuffixes[slot] : "");
+ return Void();
+ }));
+
+ ON_CALL(dynamicControl(), IsDynamicPartitionsEnabled())
+ .WillByDefault(Return(true));
+ ON_CALL(dynamicControl(), GetDeviceDir(_))
+ .WillByDefault(Invoke([](auto path) {
+ *path = kFakeDevicePath;
+ return true;
+ }));
+ }
+
+ // Return the mocked HAL module.
+ NiceMock<MockBootControlHal>& module() {
+ return static_cast<NiceMock<MockBootControlHal>&>(*bootctl_.module_);
+ }
+
+ // Return the mocked DynamicPartitionControlInterface.
+ NiceMock<MockDynamicPartitionControl>& dynamicControl() {
+ return static_cast<NiceMock<MockDynamicPartitionControl>&>(
+ *bootctl_.dynamic_control_);
+ }
+
+ // Set the fake metadata to return when LoadMetadataBuilder is called on
+ // |slot|.
+ void SetMetadata(uint32_t slot, const PartitionSizes& sizes) {
+ EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(), slot))
+ .WillOnce(
+ Invoke([sizes](auto, auto) { return NewFakeMetadata(sizes); }));
+ }
+
+ // Expect that MapPartitionOnDeviceMapper is called on target() metadata slot
+ // with each partition in |partitions|.
+ void ExpectMap(const std::set<std::string>& partitions) {
+ // Error when MapPartitionOnDeviceMapper is called on unknown arguments.
+ ON_CALL(dynamicControl(), MapPartitionOnDeviceMapper(_, _, _, _))
+ .WillByDefault(Return(false));
+
+ for (const auto& partition : partitions) {
+ EXPECT_CALL(
+ dynamicControl(),
+ MapPartitionOnDeviceMapper(GetSuperDevice(), partition, target(), _))
+ .WillOnce(Invoke([this](auto, auto partition, auto, auto path) {
+ auto it = mapped_devices_.find(partition);
+ if (it != mapped_devices_.end()) {
+ *path = it->second;
+ return true;
+ }
+ mapped_devices_[partition] = *path = kFakeMappedPath + partition;
+ return true;
+ }));
+ }
+ }
+
+ // Expect that UnmapPartitionOnDeviceMapper is called on target() metadata
+ // slot with each partition in |partitions|.
+ void ExpectUnmap(const std::set<std::string>& partitions) {
+ // Error when UnmapPartitionOnDeviceMapper is called on unknown arguments.
+ ON_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(_, _))
+ .WillByDefault(Return(false));
+
+ for (const auto& partition : partitions) {
+ EXPECT_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(partition, _))
+ .WillOnce(Invoke([this](auto partition, auto) {
+ mapped_devices_.erase(partition);
+ return true;
+ }));
+ }
+ }
+
+ void ExpectRemap(const std::set<std::string>& partitions) {
+ ExpectUnmap(partitions);
+ ExpectMap(partitions);
+ }
+
+ void ExpectDevicesAreMapped(const std::set<std::string>& partitions) {
+ ASSERT_EQ(partitions.size(), mapped_devices_.size());
+ for (const auto& partition : partitions) {
+ EXPECT_THAT(mapped_devices_, Contains(Key(Eq(partition))))
+ << "Expect that " << partition << " is mapped, but it is not.";
+ }
+ }
+
+ uint32_t source() { return slots_.source; }
+
+ uint32_t target() { return slots_.target; }
+
+ // Return partition names with suffix of source().
+ std::string S(const std::string& name) {
+ return name + std::string(kSlotSuffixes[source()]);
+ }
+
+ // Return partition names with suffix of target().
+ std::string T(const std::string& name) {
+ return name + std::string(kSlotSuffixes[target()]);
+ }
+
+ // Set source and target slots to use before testing.
+ void SetSlots(const TestParam& slots) {
+ slots_ = slots;
+
+ ON_CALL(module(), getCurrentSlot()).WillByDefault(Invoke([this] {
+ return source();
+ }));
+ // Should not store metadata to source slot.
+ EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, source()))
+ .Times(0);
+ }
+
+ BootControlAndroid bootctl_; // BootControlAndroid under test.
+ TestParam slots_;
+ // mapped devices through MapPartitionOnDeviceMapper.
+ std::map<std::string, std::string> mapped_devices_;
+};
+
+class BootControlAndroidTestP
+ : public BootControlAndroidTest,
+ public ::testing::WithParamInterface<TestParam> {
+ public:
+ void SetUp() override {
+ BootControlAndroidTest::SetUp();
+ SetSlots(GetParam());
+ }
+};
+
+// Test no resize if no dynamic partitions at all.
+TEST_P(BootControlAndroidTestP, NoResizeIfNoDynamicPartitions) {
+ SetMetadata(source(), {});
+ SetMetadata(target(), {});
+ // Should not need to resize and store metadata
+ EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target()))
+ .Times(0);
+ EXPECT_CALL(dynamicControl(), DeviceExists(Eq(GetDevice("static_a"))))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(dynamicControl(), DeviceExists(Eq(GetDevice("static_b"))))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(), {{"static", 1_GiB}}));
+ ExpectDevicesAreMapped({});
+}
+
+// Test no resize if update manifest does not contain any dynamic partitions
+TEST_P(BootControlAndroidTestP, NoResizeIfEmptyMetadata) {
+ SetMetadata(source(),
+ {{S("system"), 4_GiB},
+ {S("vendor"), 100_MiB},
+ {T("system"), 3_GiB},
+ {T("vendor"), 150_MiB}});
+ SetMetadata(target(),
+ {{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 3_GiB},
+ {T("vendor"), 150_MiB}});
+ // Should not need to resize and store metadata
+ EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target()))
+ .Times(0);
+ EXPECT_CALL(dynamicControl(), DeviceExists(Eq(GetDevice("static_a"))))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(dynamicControl(), DeviceExists(Eq(GetDevice("static_b"))))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(), {{"static", 1_GiB}}));
+ ExpectDevicesAreMapped({});
+}
+
+// Do not resize if manifest size matches size in target metadata. When resuming
+// from an update, do not redo the resize if not needed.
+TEST_P(BootControlAndroidTestP, NoResizeIfSizeMatchWhenResizing) {
+ SetMetadata(source(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}});
+ SetMetadata(target(),
+ {{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 3_GiB},
+ {T("vendor"), 1_GiB}});
+ // Should not need to resize and store metadata
+ EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target()))
+ .Times(0);
+ ExpectRemap({T("system"), T("vendor")});
+
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+ target(), {{"system", 3_GiB}, {"vendor", 1_GiB}}));
+ ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Do not resize if manifest size matches size in target metadata. When resuming
+// from an update, do not redo the resize if not needed.
+TEST_P(BootControlAndroidTestP, NoResizeIfSizeMatchWhenAdding) {
+ SetMetadata(source(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}});
+ SetMetadata(
+ target(),
+ {{S("system"), 2_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}});
+ // Should not need to resize and store metadata
+ EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target()))
+ .Times(0);
+ ExpectRemap({T("system"), T("vendor")});
+
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+ target(), {{"system", 2_GiB}, {"vendor", 1_GiB}}));
+ ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Do not resize if manifest size matches size in target metadata. When resuming
+// from an update, do not redo the resize if not needed.
+TEST_P(BootControlAndroidTestP, NoResizeIfSizeMatchWhenDeleting) {
+ SetMetadata(source(),
+ {{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 2_GiB},
+ {T("vendor"), 1_GiB}});
+ SetMetadata(target(),
+ {{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 2_GiB},
+ {T("vendor"), 0}});
+ // Should not need to resize and store metadata
+ EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target()))
+ .Times(0);
+ ExpectUnmap({T("system"), T("vendor")});
+ ExpectMap({T("system")});
+
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+ target(), {{"system", 2_GiB}, {"vendor", 0}}));
+ ExpectDevicesAreMapped({T("system")});
+}
+
+// Test resize case. Grow if target metadata contains a partition with a size
+// less than expected.
+TEST_P(BootControlAndroidTestP, NeedGrowIfSizeNotMatchWhenResizing) {
+ PartitionSizes initial{{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 2_GiB},
+ {T("vendor"), 1_GiB}};
+ SetMetadata(source(), initial);
+ SetMetadata(target(), initial);
+ EXPECT_CALL(dynamicControl(),
+ StoreMetadata(GetSuperDevice(),
+ MetadataMatches({{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 3_GiB},
+ {T("vendor"), 1_GiB}}),
+ target()))
+ .WillOnce(Return(true));
+ ExpectRemap({T("system"), T("vendor")});
+
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+ target(), {{"system", 3_GiB}, {"vendor", 1_GiB}}));
+ ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Test resize case. Shrink if target metadata contains a partition with a size
+// greater than expected.
+TEST_P(BootControlAndroidTestP, NeedShrinkIfSizeNotMatchWhenResizing) {
+ PartitionSizes initial{{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 2_GiB},
+ {T("vendor"), 1_GiB}};
+ SetMetadata(source(), initial);
+ SetMetadata(target(), initial);
+ EXPECT_CALL(dynamicControl(),
+ StoreMetadata(GetSuperDevice(),
+ MetadataMatches({{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 2_GiB},
+ {T("vendor"), 150_MiB}}),
+ target()))
+ .WillOnce(Return(true));
+ ExpectRemap({T("system"), T("vendor")});
+
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+ target(), {{"system", 2_GiB}, {"vendor", 150_MiB}}));
+ ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Test adding partitions on the first run.
+TEST_P(BootControlAndroidTestP, AddPartitionToEmptyMetadata) {
+ SetMetadata(source(), {});
+ SetMetadata(target(), {});
+ EXPECT_CALL(dynamicControl(),
+ StoreMetadata(
+ GetSuperDevice(),
+ MetadataMatches({{T("system"), 2_GiB}, {T("vendor"), 1_GiB}}),
+ target()))
+ .WillOnce(Return(true));
+ ExpectRemap({T("system"), T("vendor")});
+
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+ target(), {{"system", 2_GiB}, {"vendor", 1_GiB}}));
+ ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Test subsequent add case.
+TEST_P(BootControlAndroidTestP, AddAdditionalPartition) {
+ SetMetadata(source(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}});
+ SetMetadata(target(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}});
+ EXPECT_CALL(dynamicControl(),
+ StoreMetadata(GetSuperDevice(),
+ MetadataMatches({{S("system"), 2_GiB},
+ {T("system"), 2_GiB},
+ {T("vendor"), 1_GiB}}),
+ target()))
+ .WillOnce(Return(true));
+ ExpectRemap({T("system"), T("vendor")});
+
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+ target(), {{"system", 2_GiB}, {"vendor", 1_GiB}}));
+ ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Test delete one partition.
+TEST_P(BootControlAndroidTestP, DeletePartition) {
+ PartitionSizes initial{{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 2_GiB},
+ {T("vendor"), 1_GiB}};
+ SetMetadata(source(), initial);
+ SetMetadata(target(), initial);
+ EXPECT_CALL(dynamicControl(),
+ StoreMetadata(GetSuperDevice(),
+ MetadataMatches({{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 2_GiB},
+ {T("vendor"), 0}}),
+ target()))
+ .WillOnce(Return(true));
+ ExpectUnmap({T("system"), T("vendor")});
+ ExpectMap({T("system")});
+
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+ target(), {{"system", 2_GiB}, {"vendor", 0}}));
+ ExpectDevicesAreMapped({T("system")});
+}
+
+// Test delete all partitions.
+TEST_P(BootControlAndroidTestP, DeleteAll) {
+ PartitionSizes initial{{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 2_GiB},
+ {T("vendor"), 1_GiB}};
+ SetMetadata(source(), initial);
+ SetMetadata(target(), initial);
+ EXPECT_CALL(dynamicControl(),
+ StoreMetadata(GetSuperDevice(),
+ MetadataMatches({{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 0},
+ {T("vendor"), 0}}),
+ target()))
+ .WillOnce(Return(true));
+ ExpectUnmap({T("system"), T("vendor")});
+ ExpectMap({});
+
+ EXPECT_TRUE(
+ bootctl_.InitPartitionMetadata(target(), {{"system", 0}, {"vendor", 0}}));
+ ExpectDevicesAreMapped({});
+}
+
+// Test corrupt source metadata case. This shouldn't happen in practice,
+// because the device is already booted normally.
+TEST_P(BootControlAndroidTestP, CorruptedSourceMetadata) {
+ EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(), source()))
+ .WillOnce(Invoke([](auto, auto) { return nullptr; }));
+ EXPECT_FALSE(bootctl_.InitPartitionMetadata(target(), {}))
+ << "Should not be able to continue with corrupt source metadata";
+}
+
+// Test corrupt target metadata case. This may happen in practice.
+// BootControlAndroid should copy from source metadata and make necessary
+// modifications on it.
+TEST_P(BootControlAndroidTestP, CorruptedTargetMetadata) {
+ SetMetadata(source(),
+ {{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 0},
+ {T("vendor"), 0}});
+ EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(), target()))
+ .WillOnce(Invoke([](auto, auto) { return nullptr; }));
+ EXPECT_CALL(dynamicControl(),
+ StoreMetadata(GetSuperDevice(),
+ MetadataMatches({{S("system"), 2_GiB},
+ {S("vendor"), 1_GiB},
+ {T("system"), 3_GiB},
+ {T("vendor"), 150_MiB}}),
+ target()))
+ .WillOnce(Return(true));
+ ExpectRemap({T("system"), T("vendor")});
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+ target(), {{"system", 3_GiB}, {"vendor", 150_MiB}}));
+ ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Test that InitPartitionMetadata fail if there is not enough space on the
+// device.
+TEST_P(BootControlAndroidTestP, NotEnoughSpace) {
+ PartitionSizes initial{{S("system"), 3_GiB},
+ {S("vendor"), 2_GiB},
+ {T("system"), 0},
+ {T("vendor"), 0}};
+ SetMetadata(source(), initial);
+ SetMetadata(target(), initial);
+ EXPECT_FALSE(bootctl_.InitPartitionMetadata(
+ target(), {{"system", 3_GiB}, {"vendor", 3_GiB}}))
+ << "Should not be able to fit 11GiB data into 10GiB space";
+}
+
+INSTANTIATE_TEST_CASE_P(ParamTest,
+ BootControlAndroidTestP,
+ testing::Values(TestParam{0, 1}, TestParam{1, 0}));
+
+const PartitionSizes update_sizes_0() {
+ return {{"grown_a", 2_GiB},
+ {"shrunk_a", 1_GiB},
+ {"same_a", 100_MiB},
+ {"deleted_a", 150_MiB},
+ {"grown_b", 200_MiB},
+ {"shrunk_b", 0},
+ {"same_b", 0}};
+}
+
+const PartitionSizes update_sizes_1() {
+ return {
+ {"grown_a", 2_GiB},
+ {"shrunk_a", 1_GiB},
+ {"same_a", 100_MiB},
+ {"deleted_a", 150_MiB},
+ {"grown_b", 3_GiB},
+ {"shrunk_b", 150_MiB},
+ {"same_b", 100_MiB},
+ {"added_b", 150_MiB},
+ {"deleted_b", 0},
+ };
+}
+
+const PartitionSizes update_sizes_2() {
+ return {{"grown_a", 4_GiB},
+ {"shrunk_a", 100_MiB},
+ {"same_a", 100_MiB},
+ {"added_a", 0_MiB},
+ {"deleted_a", 64_MiB},
+ {"grown_b", 3_GiB},
+ {"shrunk_b", 150_MiB},
+ {"same_b", 100_MiB},
+ {"added_b", 150_MiB},
+ {"deleted_b", 0}};
+}
+
+// Test case for first update after the device is manufactured, in which
+// case the "other" slot is likely of size "0" (except system, which is
+// non-zero because of system_other partition)
+TEST_F(BootControlAndroidTest, SimulatedFirstUpdate) {
+ SetSlots({0, 1});
+
+ SetMetadata(source(), update_sizes_0());
+ SetMetadata(target(), update_sizes_0());
+ EXPECT_CALL(
+ dynamicControl(),
+ StoreMetadata(
+ GetSuperDevice(), MetadataMatches(update_sizes_1()), target()))
+ .WillOnce(Return(true));
+ ExpectUnmap({"grown_b", "shrunk_b", "same_b", "added_b", "deleted_b"});
+ ExpectMap({"grown_b", "shrunk_b", "same_b", "added_b"});
+
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(),
+ {{"grown", 3_GiB},
+ {"shrunk", 150_MiB},
+ {"same", 100_MiB},
+ {"added", 150_MiB},
+ {"deleted", 0_MiB}}));
+ ExpectDevicesAreMapped({"grown_b", "shrunk_b", "same_b", "added_b"});
+}
+
+// After first update, test for the second update. In the second update, the
+// "added" partition is deleted and "deleted" partition is re-added.
+TEST_F(BootControlAndroidTest, SimulatedSecondUpdate) {
+ SetSlots({1, 0});
+
+ SetMetadata(source(), update_sizes_1());
+ SetMetadata(target(), update_sizes_0());
+
+ EXPECT_CALL(
+ dynamicControl(),
+ StoreMetadata(
+ GetSuperDevice(), MetadataMatches(update_sizes_2()), target()))
+ .WillOnce(Return(true));
+ ExpectUnmap({"grown_a", "shrunk_a", "same_a", "added_a", "deleted_a"});
+ ExpectMap({"grown_a", "shrunk_a", "same_a", "deleted_a"});
+
+ EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(),
+ {{"grown", 4_GiB},
+ {"shrunk", 100_MiB},
+ {"same", 100_MiB},
+ {"added", 0_MiB},
+ {"deleted", 64_MiB}}));
+ ExpectDevicesAreMapped({"grown_a", "shrunk_a", "same_a", "deleted_a"});
+}
+
+TEST_F(BootControlAndroidTest, ApplyingToCurrentSlot) {
+ SetSlots({1, 1});
+ EXPECT_FALSE(bootctl_.InitPartitionMetadata(target(), {}))
+ << "Should not be able to apply to current slot.";
+}
+
+} // namespace chromeos_update_engine
diff --git a/boot_control_chromeos.cc b/boot_control_chromeos.cc
index 7d8f068..68ac5bd 100644
--- a/boot_control_chromeos.cc
+++ b/boot_control_chromeos.cc
@@ -302,4 +302,11 @@
return -1;
}
+bool BootControlChromeOS::InitPartitionMetadata(
+ Slot slot, const PartitionSizes& partition_sizes) {
+ return true;
+}
+
+void BootControlChromeOS::Cleanup() {}
+
} // namespace chromeos_update_engine
diff --git a/boot_control_chromeos.h b/boot_control_chromeos.h
index a1d57fe..d7bab05 100644
--- a/boot_control_chromeos.h
+++ b/boot_control_chromeos.h
@@ -50,6 +50,9 @@
bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+ bool InitPartitionMetadata(Slot slot,
+ const PartitionSizes& partition_sizes) override;
+ void Cleanup() override;
private:
friend class BootControlChromeOSTest;
diff --git a/boot_control_recovery.cc b/boot_control_recovery.cc
index 722d32a..c6e3ffd 100644
--- a/boot_control_recovery.cc
+++ b/boot_control_recovery.cc
@@ -183,4 +183,18 @@
brillo::MessageLoop::kTaskIdNull;
}
+// TODO(b/78598708): Remove BootControlRecovery, so don't bother resizing
+// partitions here.
+bool BootControlRecovery::InitPartitionMetadata(
+ Slot slot, const PartitionSizes& partition_sizes) {
+ LOG(ERROR) << __FUNCTION__
+ << " is not implemented. BootControlRecovery will be removed.";
+ return true;
+}
+
+void BootControlRecovery::Cleanup() {
+ LOG(ERROR) << __FUNCTION__
+ << " is not implemented. BootControlRecovery will be removed.";
+}
+
} // namespace chromeos_update_engine
diff --git a/boot_control_recovery.h b/boot_control_recovery.h
index 3a83caa..c8b02bb 100644
--- a/boot_control_recovery.h
+++ b/boot_control_recovery.h
@@ -49,6 +49,9 @@
bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+ bool InitPartitionMetadata(Slot slot,
+ const PartitionSizes& partition_sizes) override;
+ void Cleanup() override;
private:
// NOTE: There is no way to release/unload HAL implementations so
diff --git a/common/boot_control_interface.h b/common/boot_control_interface.h
index 659b388..776333c 100644
--- a/common/boot_control_interface.h
+++ b/common/boot_control_interface.h
@@ -18,6 +18,7 @@
#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_INTERFACE_H_
#include <climits>
+#include <map>
#include <string>
#include <base/callback.h>
@@ -32,6 +33,7 @@
class BootControlInterface {
public:
using Slot = unsigned int;
+ using PartitionSizes = std::map<std::string, uint64_t>;
static const Slot kInvalidSlot = UINT_MAX;
@@ -77,6 +79,17 @@
// of the operation.
virtual bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) = 0;
+ // Initialize metadata of underlying partitions for a given |slot|.
+ // Ensure that partitions at the specified |slot| has a given size, as
+ // specified by |partition_sizes|. |partition_sizes| has the format:
+ // {"vendor": 524288000, "system": 2097152000, ...}; values must be
+ // aligned to the logical block size of the super partition.
+ virtual bool InitPartitionMetadata(Slot slot,
+ const PartitionSizes& partition_sizes) = 0;
+
+ // Do necessary clean-up operations after the whole update.
+ virtual void Cleanup() = 0;
+
// Return a human-readable slot name used for logging.
static std::string SlotName(Slot slot) {
if (slot == kInvalidSlot)
diff --git a/common/boot_control_stub.cc b/common/boot_control_stub.cc
index 2de0c82..2e326e5 100644
--- a/common/boot_control_stub.cc
+++ b/common/boot_control_stub.cc
@@ -59,4 +59,14 @@
return false;
}
+bool BootControlStub::InitPartitionMetadata(
+ Slot slot, const PartitionSizes& partition_sizes) {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return false;
+}
+
+void BootControlStub::Cleanup() {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+}
+
} // namespace chromeos_update_engine
diff --git a/common/boot_control_stub.h b/common/boot_control_stub.h
index 9e3b05c..65248af 100644
--- a/common/boot_control_stub.h
+++ b/common/boot_control_stub.h
@@ -45,6 +45,9 @@
bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+ bool InitPartitionMetadata(Slot slot,
+ const PartitionSizes& partition_sizes) override;
+ void Cleanup() override;
private:
DISALLOW_COPY_AND_ASSIGN(BootControlStub);
diff --git a/common/fake_boot_control.h b/common/fake_boot_control.h
index 3eccc80..e71c83a 100644
--- a/common/fake_boot_control.h
+++ b/common/fake_boot_control.h
@@ -74,6 +74,13 @@
return true;
}
+ bool InitPartitionMetadata(Slot slot,
+ const PartitionSizes& partition_sizes) override {
+ return true;
+ }
+
+ void Cleanup() override {}
+
// Setters
void SetNumSlots(unsigned int num_slots) {
num_slots_ = num_slots;
diff --git a/dynamic_partition_control_android.cc b/dynamic_partition_control_android.cc
new file mode 100644
index 0000000..27e117c
--- /dev/null
+++ b/dynamic_partition_control_android.cc
@@ -0,0 +1,195 @@
+//
+// 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 "update_engine/dynamic_partition_control_android.h"
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <bootloader_message/bootloader_message.h>
+#include <fs_mgr_dm_linear.h>
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/utils.h"
+
+using android::base::GetBoolProperty;
+using android::base::Join;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::fs_mgr::CreateLogicalPartition;
+using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::MetadataBuilder;
+
+namespace chromeos_update_engine {
+
+constexpr char kUseDynamicPartitions[] = "ro.boot.logical_partitions";
+constexpr uint64_t kMapTimeoutMillis = 1000;
+
+DynamicPartitionControlAndroid::~DynamicPartitionControlAndroid() {
+ CleanupInternal(false /* wait */);
+}
+
+bool DynamicPartitionControlAndroid::IsDynamicPartitionsEnabled() {
+ return GetBoolProperty(kUseDynamicPartitions, false);
+}
+
+bool DynamicPartitionControlAndroid::MapPartitionOnDeviceMapper(
+ const std::string& super_device,
+ const std::string& target_partition_name,
+ uint32_t slot,
+ std::string* path) {
+ if (!CreateLogicalPartition(super_device.c_str(),
+ slot,
+ target_partition_name,
+ true /* force_writable */,
+ std::chrono::milliseconds(kMapTimeoutMillis),
+ path)) {
+ LOG(ERROR) << "Cannot map " << target_partition_name << " in "
+ << super_device << " on device mapper.";
+ return false;
+ }
+ LOG(INFO) << "Succesfully mapped " << target_partition_name
+ << " to device mapper; device path at " << *path;
+ mapped_devices_.insert(target_partition_name);
+ return true;
+}
+
+bool DynamicPartitionControlAndroid::UnmapPartitionOnDeviceMapper(
+ const std::string& target_partition_name, bool wait) {
+ if (DeviceMapper::Instance().GetState(target_partition_name) !=
+ DmDeviceState::INVALID) {
+ if (!DestroyLogicalPartition(
+ target_partition_name,
+ std::chrono::milliseconds(wait ? kMapTimeoutMillis : 0))) {
+ LOG(ERROR) << "Cannot unmap " << target_partition_name
+ << " from device mapper.";
+ return false;
+ }
+ LOG(INFO) << "Successfully unmapped " << target_partition_name
+ << " from device mapper.";
+ }
+ mapped_devices_.erase(target_partition_name);
+ return true;
+}
+
+void DynamicPartitionControlAndroid::CleanupInternal(bool wait) {
+ // UnmapPartitionOnDeviceMapper removes objects from mapped_devices_, hence
+ // a copy is needed for the loop.
+ std::set<std::string> mapped = mapped_devices_;
+ LOG(INFO) << "Destroying [" << Join(mapped, ", ") << "] from device mapper";
+ for (const auto& partition_name : mapped) {
+ ignore_result(UnmapPartitionOnDeviceMapper(partition_name, wait));
+ }
+}
+
+void DynamicPartitionControlAndroid::Cleanup() {
+ CleanupInternal(true /* wait */);
+}
+
+bool DynamicPartitionControlAndroid::DeviceExists(const std::string& path) {
+ return base::PathExists(base::FilePath(path));
+}
+
+android::dm::DmDeviceState DynamicPartitionControlAndroid::GetState(
+ const std::string& name) {
+ return DeviceMapper::Instance().GetState(name);
+}
+
+bool DynamicPartitionControlAndroid::GetDmDevicePathByName(
+ const std::string& name, std::string* path) {
+ return DeviceMapper::Instance().GetDmDevicePathByName(name, path);
+}
+
+std::unique_ptr<MetadataBuilder>
+DynamicPartitionControlAndroid::LoadMetadataBuilder(
+ const std::string& super_device, uint32_t source_slot) {
+ auto builder = MetadataBuilder::New(super_device, source_slot);
+ if (builder == nullptr) {
+ LOG(WARNING) << "No metadata slot "
+ << BootControlInterface::SlotName(source_slot) << " in "
+ << super_device;
+ }
+ LOG(INFO) << "Loaded metadata from slot "
+ << BootControlInterface::SlotName(source_slot) << " in "
+ << super_device;
+ return builder;
+}
+
+bool DynamicPartitionControlAndroid::StoreMetadata(
+ const std::string& super_device,
+ MetadataBuilder* builder,
+ uint32_t target_slot) {
+ auto metadata = builder->Export();
+ if (metadata == nullptr) {
+ LOG(ERROR) << "Cannot export metadata to slot "
+ << BootControlInterface::SlotName(target_slot) << " in "
+ << super_device;
+ return false;
+ }
+
+ if (!UpdatePartitionTable(super_device, *metadata, target_slot)) {
+ LOG(ERROR) << "Cannot write metadata to slot "
+ << BootControlInterface::SlotName(target_slot) << " in "
+ << super_device;
+ return false;
+ }
+
+ LOG(INFO) << "Copied metadata to slot "
+ << BootControlInterface::SlotName(target_slot) << " in "
+ << super_device;
+ return true;
+}
+
+bool DynamicPartitionControlAndroid::GetDeviceDir(std::string* out) {
+ // We can't use fs_mgr to look up |partition_name| because fstab
+ // doesn't list every slot partition (it uses the slotselect option
+ // to mask the suffix).
+ //
+ // We can however assume that there's an entry for the /misc mount
+ // point and use that to get the device file for the misc
+ // partition. This helps us locate the disk that |partition_name|
+ // resides on. From there we'll assume that a by-name scheme is used
+ // so we can just replace the trailing "misc" by the given
+ // |partition_name| and suffix corresponding to |slot|, e.g.
+ //
+ // /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
+ // /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a
+ //
+ // If needed, it's possible to relax the by-name assumption in the
+ // future by trawling /sys/block looking for the appropriate sibling
+ // of misc and then finding an entry in /dev matching the sysfs
+ // entry.
+
+ std::string err, misc_device = get_bootloader_message_blk_device(&err);
+ if (misc_device.empty()) {
+ LOG(ERROR) << "Unable to get misc block device: " << err;
+ return false;
+ }
+
+ if (!utils::IsSymlink(misc_device.c_str())) {
+ LOG(ERROR) << "Device file " << misc_device << " for /misc "
+ << "is not a symlink.";
+ return false;
+ }
+ *out = base::FilePath(misc_device).DirName().value();
+ return true;
+}
+} // namespace chromeos_update_engine
diff --git a/dynamic_partition_control_android.h b/dynamic_partition_control_android.h
new file mode 100644
index 0000000..945954d
--- /dev/null
+++ b/dynamic_partition_control_android.h
@@ -0,0 +1,61 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_ANDROID_H_
+#define UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_ANDROID_H_
+
+#include "update_engine/dynamic_partition_control_interface.h"
+
+#include <memory>
+#include <set>
+#include <string>
+
+namespace chromeos_update_engine {
+
+class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface {
+ public:
+ DynamicPartitionControlAndroid() = default;
+ ~DynamicPartitionControlAndroid();
+ bool IsDynamicPartitionsEnabled() override;
+ bool MapPartitionOnDeviceMapper(const std::string& super_device,
+ const std::string& target_partition_name,
+ uint32_t slot,
+ std::string* path) override;
+ bool UnmapPartitionOnDeviceMapper(const std::string& target_partition_name,
+ bool wait) override;
+ void Cleanup() override;
+ bool DeviceExists(const std::string& path) override;
+ android::dm::DmDeviceState GetState(const std::string& name) override;
+ bool GetDmDevicePathByName(const std::string& name,
+ std::string* path) override;
+ std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder(
+ const std::string& super_device, uint32_t source_slot) override;
+ bool StoreMetadata(const std::string& super_device,
+ android::fs_mgr::MetadataBuilder* builder,
+ uint32_t target_slot) override;
+ bool GetDeviceDir(std::string* path) override;
+
+ private:
+ std::set<std::string> mapped_devices_;
+
+ void CleanupInternal(bool wait);
+
+ DISALLOW_COPY_AND_ASSIGN(DynamicPartitionControlAndroid);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_ANDROID_H_
diff --git a/dynamic_partition_control_interface.h b/dynamic_partition_control_interface.h
new file mode 100644
index 0000000..00ed026
--- /dev/null
+++ b/dynamic_partition_control_interface.h
@@ -0,0 +1,90 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_
+#define UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include <base/files/file_util.h>
+#include <libdm/dm.h>
+#include <liblp/builder.h>
+
+namespace chromeos_update_engine {
+
+class DynamicPartitionControlInterface {
+ public:
+ virtual ~DynamicPartitionControlInterface() = default;
+
+ // Return true iff dynamic partitions is enabled on this device.
+ virtual bool IsDynamicPartitionsEnabled() = 0;
+
+ // Map logical partition on device-mapper.
+ // |super_device| is the device path of the physical partition ("super").
+ // |target_partition_name| is the identifier used in metadata; for example,
+ // "vendor_a"
+ // |slot| is the selected slot to mount; for example, 0 for "_a".
+ // Returns true if mapped successfully; if so, |path| is set to the device
+ // path of the mapped logical partition.
+ virtual bool MapPartitionOnDeviceMapper(
+ const std::string& super_device,
+ const std::string& target_partition_name,
+ uint32_t slot,
+ std::string* path) = 0;
+
+ // Unmap logical partition on device mapper. This is the reverse operation
+ // of MapPartitionOnDeviceMapper.
+ // If |wait| is set, wait until the device is unmapped.
+ // Returns true if unmapped successfully.
+ virtual bool UnmapPartitionOnDeviceMapper(
+ const std::string& target_partition_name, bool wait) = 0;
+
+ // Do necessary cleanups before destroying the object.
+ virtual void Cleanup() = 0;
+
+ // Return true if a static partition exists at device path |path|.
+ virtual bool DeviceExists(const std::string& path) = 0;
+
+ // Returns the current state of the underlying device mapper device
+ // with given name.
+ // One of INVALID, SUSPENDED or ACTIVE.
+ virtual android::dm::DmDeviceState GetState(const std::string& name) = 0;
+
+ // Returns the path to the device mapper device node in '/dev' corresponding
+ // to 'name'. If the device does not exist, false is returned, and the path
+ // parameter is not set.
+ virtual bool GetDmDevicePathByName(const std::string& name,
+ std::string* path) = 0;
+
+ // Retrieve metadata from |super_device| at slot |source_slot|.
+ virtual std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder(
+ const std::string& super_device, uint32_t source_slot) = 0;
+
+ // Write metadata |builder| to |super_device| at slot |target_slot|.
+ virtual bool StoreMetadata(const std::string& super_device,
+ android::fs_mgr::MetadataBuilder* builder,
+ uint32_t target_slot) = 0;
+
+ // Return a possible location for devices listed by name.
+ virtual bool GetDeviceDir(std::string* path) = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_
diff --git a/mock_boot_control_hal.h b/mock_boot_control_hal.h
new file mode 100644
index 0000000..4e9cb50
--- /dev/null
+++ b/mock_boot_control_hal.h
@@ -0,0 +1,49 @@
+//
+// 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 <android/hardware/boot/1.0/IBootControl.h>
+#include <stdint.h>
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+class MockBootControlHal
+ : public ::android::hardware::boot::V1_0::IBootControl {
+ public:
+ MOCK_METHOD0(getNumberSlots, ::android::hardware::Return<uint32_t>());
+ MOCK_METHOD0(getCurrentSlot, ::android::hardware::Return<uint32_t>());
+ MOCK_METHOD1(markBootSuccessful,
+ ::android::hardware::Return<void>(markBootSuccessful_cb));
+ MOCK_METHOD2(setActiveBootSlot,
+ ::android::hardware::Return<void>(uint32_t,
+ setActiveBootSlot_cb));
+ MOCK_METHOD2(setSlotAsUnbootable,
+ ::android::hardware::Return<void>(uint32_t,
+ setSlotAsUnbootable_cb));
+ MOCK_METHOD1(
+ isSlotBootable,
+ ::android::hardware::Return<::android::hardware::boot::V1_0::BoolResult>(
+ uint32_t));
+ MOCK_METHOD1(
+ isSlotMarkedSuccessful,
+ ::android::hardware::Return<::android::hardware::boot::V1_0::BoolResult>(
+ uint32_t));
+ MOCK_METHOD2(getSuffix,
+ ::android::hardware::Return<void>(uint32_t, getSuffix_cb));
+};
+
+} // namespace chromeos_update_engine
diff --git a/mock_dynamic_partition_control.h b/mock_dynamic_partition_control.h
new file mode 100644
index 0000000..ccbc7cf
--- /dev/null
+++ b/mock_dynamic_partition_control.h
@@ -0,0 +1,49 @@
+//
+// 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 <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/dynamic_partition_control_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockDynamicPartitionControl : public DynamicPartitionControlInterface {
+ public:
+ MOCK_METHOD4(
+ MapPartitionOnDeviceMapper,
+ bool(const std::string&, const std::string&, uint32_t, std::string*));
+ MOCK_METHOD2(UnmapPartitionOnDeviceMapper, bool(const std::string&, bool));
+ MOCK_METHOD0(Cleanup, void());
+ MOCK_METHOD1(DeviceExists, bool(const std::string&));
+ MOCK_METHOD1(GetState, ::android::dm::DmDeviceState(const std::string&));
+ MOCK_METHOD2(GetDmDevicePathByName, bool(const std::string&, std::string*));
+ MOCK_METHOD2(LoadMetadataBuilder,
+ std::unique_ptr<::android::fs_mgr::MetadataBuilder>(
+ const std::string&, uint32_t));
+ MOCK_METHOD3(StoreMetadata,
+ bool(const std::string&,
+ android::fs_mgr::MetadataBuilder*,
+ uint32_t));
+ MOCK_METHOD1(GetDeviceDir, bool(std::string*));
+ MOCK_METHOD0(IsDynamicPartitionsEnabled, bool());
+};
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index e700255..30fe763 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -21,6 +21,7 @@
#include <algorithm>
#include <cstring>
+#include <map>
#include <memory>
#include <string>
#include <utility>
@@ -639,9 +640,11 @@
return false;
}
- if (!OpenCurrentPartition()) {
- *error = ErrorCode::kInstallDeviceOpenError;
- return false;
+ if (next_operation_num_ < acc_num_operations_[current_partition_]) {
+ if (!OpenCurrentPartition()) {
+ *error = ErrorCode::kInstallDeviceOpenError;
+ return false;
+ }
}
if (next_operation_num_ > 0)
@@ -658,9 +661,12 @@
// We know there are more operations to perform because we didn't reach the
// |num_total_operations_| limit yet.
- while (next_operation_num_ >= acc_num_operations_[current_partition_]) {
+ if (next_operation_num_ >= acc_num_operations_[current_partition_]) {
CloseCurrentPartition();
- current_partition_++;
+ // Skip until there are operations for current_partition_.
+ while (next_operation_num_ >= acc_num_operations_[current_partition_]) {
+ current_partition_++;
+ }
if (!OpenCurrentPartition()) {
*error = ErrorCode::kInstallDeviceOpenError;
return false;
@@ -912,6 +918,20 @@
install_plan_->partitions.push_back(install_part);
}
+ if (install_plan_->target_slot != BootControlInterface::kInvalidSlot) {
+ BootControlInterface::PartitionSizes partition_sizes;
+ for (const InstallPlan::Partition& partition : install_plan_->partitions) {
+ partition_sizes.emplace(partition.name, partition.target_size);
+ }
+ if (!boot_control_->InitPartitionMetadata(install_plan_->target_slot,
+ partition_sizes)) {
+ LOG(ERROR) << "Unable to initialize partition metadata for slot "
+ << BootControlInterface::SlotName(install_plan_->target_slot);
+ *error = ErrorCode::kInstallDeviceOpenError;
+ return false;
+ }
+ }
+
if (!install_plan_->LoadPartitionsFromSlots(boot_control_)) {
LOG(ERROR) << "Unable to determine all the partition devices.";
*error = ErrorCode::kInstallDeviceOpenError;
diff --git a/payload_consumer/filesystem_verifier_action.cc b/payload_consumer/filesystem_verifier_action.cc
index a5e1e61..d74d81d 100644
--- a/payload_consumer/filesystem_verifier_action.cc
+++ b/payload_consumer/filesystem_verifier_action.cc
@@ -98,13 +98,25 @@
partition_size_ = partition.target_size;
break;
}
- LOG(INFO) << "Hashing partition " << partition_index_ << " ("
- << partition.name << ") on device " << part_path;
+
if (part_path.empty()) {
+ if (partition_size_ == 0) {
+ LOG(INFO) << "Skip hashing partition " << partition_index_ << " ("
+ << partition.name << ") because size is 0.";
+ partition_index_++;
+ StartPartitionHashing();
+ return;
+ }
+ LOG(ERROR) << "Cannot hash partition " << partition_index_ << " ("
+ << partition.name
+ << ") because its device path cannot be determined.";
Cleanup(ErrorCode::kFilesystemVerifierError);
return;
}
+ LOG(INFO) << "Hashing partition " << partition_index_ << " ("
+ << partition.name << ") on device " << part_path;
+
brillo::ErrorPtr error;
src_stream_ = brillo::FileStream::Open(
base::FilePath(part_path),
diff --git a/payload_consumer/install_plan.cc b/payload_consumer/install_plan.cc
index 45112d6..561e294 100644
--- a/payload_consumer/install_plan.cc
+++ b/payload_consumer/install_plan.cc
@@ -103,7 +103,8 @@
partition.source_path.clear();
}
- if (target_slot != BootControlInterface::kInvalidSlot) {
+ if (target_slot != BootControlInterface::kInvalidSlot &&
+ partition.target_size > 0) {
result = boot_control->GetPartitionDevice(
partition.name, target_slot, &partition.target_path) && result;
} else {
diff --git a/update_attempter_android.cc b/update_attempter_android.cc
index 4d21de6..97ff6d6 100644
--- a/update_attempter_android.cc
+++ b/update_attempter_android.cc
@@ -558,6 +558,8 @@
return;
}
+ boot_control_->Cleanup();
+
download_progress_ = 0;
UpdateStatus new_status =
(error_code == ErrorCode::kSuccess ? UpdateStatus::UPDATED_NEED_REBOOT