DO NOT MERGE - Merge ab/7272582
Bug: 190855093
Change-Id: Iec6dbb2c309ca96e03b505f5d7e642d5593a6052
diff --git a/Android.bp b/Android.bp
index 7cdd64c..d74e78f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -66,7 +66,7 @@
],
include_dirs: ["system"],
local_include_dirs: ["client_library/include"],
- static_libs: ["libgtest_prod"],
+ header_libs: ["libgtest_prod_headers"],
shared_libs: [
"libbrillo-stream",
"libbrillo",
@@ -348,6 +348,9 @@
"libstatslog",
"libutils",
],
+ whole_static_libs: [
+ "com.android.sysprop.apex",
+ ],
}
cc_library_static {
@@ -426,6 +429,7 @@
// TODO(deymo): Remove external/cros/system_api/dbus once the strings are moved
// out of the DBus interface.
include_dirs: ["external/cros/system_api/dbus"],
+ header_libs: ["libgtest_prod_headers"],
srcs: [
"aosp/hardware_android.cc",
@@ -460,7 +464,6 @@
"gkiprops",
"libevent",
"libmodpb64",
- "libgtest_prod",
"libprotobuf-cpp-lite",
"libbrillo-stream",
"libbrillo",
@@ -789,6 +792,7 @@
"libcurl_http_fetcher_unittest.cc",
"payload_consumer/bzip_extent_writer_unittest.cc",
"payload_consumer/cached_file_descriptor_unittest.cc",
+ "payload_consumer/cow_writer_file_descriptor_unittest.cc",
"payload_consumer/certificate_parser_android_unittest.cc",
"payload_consumer/delta_performer_integration_test.cc",
"payload_consumer/delta_performer_unittest.cc",
@@ -896,4 +900,4 @@
"libz",
"update_metadata-protos",
],
-}
\ No newline at end of file
+}
diff --git a/aosp/apex_handler_android.cc b/aosp/apex_handler_android.cc
index 38ec410..8beef96 100644
--- a/aosp/apex_handler_android.cc
+++ b/aosp/apex_handler_android.cc
@@ -14,10 +14,13 @@
// limitations under the License.
//
+#include <memory>
#include <utility>
#include <base/files/file_util.h>
+#include <ApexProperties.sysprop.h>
+
#include "update_engine/aosp/apex_handler_android.h"
#include "update_engine/common/utils.h"
@@ -44,6 +47,14 @@
} // namespace
+std::unique_ptr<ApexHandlerInterface> CreateApexHandler() {
+ if (android::sysprop::ApexProperties::updatable().value_or(false)) {
+ return std::make_unique<ApexHandlerAndroid>();
+ } else {
+ return std::make_unique<FlattenedApexHandlerAndroid>();
+ }
+}
+
android::base::Result<uint64_t> ApexHandlerAndroid::CalculateSize(
const std::vector<ApexInfo>& apex_infos) const {
// We might not need to decompress every APEX. Communicate with apexd to get
@@ -86,4 +97,14 @@
return android::interface_cast<android::apex::IApexService>(binder);
}
+android::base::Result<uint64_t> FlattenedApexHandlerAndroid::CalculateSize(
+ const std::vector<ApexInfo>& apex_infos) const {
+ return 0;
+}
+
+bool FlattenedApexHandlerAndroid::AllocateSpace(
+ const std::vector<ApexInfo>& apex_infos) const {
+ return true;
+}
+
} // namespace chromeos_update_engine
diff --git a/aosp/apex_handler_android.h b/aosp/apex_handler_android.h
index 00f3a80..767f561 100644
--- a/aosp/apex_handler_android.h
+++ b/aosp/apex_handler_android.h
@@ -17,6 +17,7 @@
#ifndef SYSTEM_UPDATE_ENGINE_AOSP_APEX_HANDLER_ANDROID_H_
#define SYSTEM_UPDATE_ENGINE_AOSP_APEX_HANDLER_ANDROID_H_
+#include <memory>
#include <string>
#include <vector>
@@ -28,6 +29,8 @@
namespace chromeos_update_engine {
+std::unique_ptr<ApexHandlerInterface> CreateApexHandler();
+
class ApexHandlerAndroid : virtual public ApexHandlerInterface {
public:
android::base::Result<uint64_t> CalculateSize(
@@ -38,6 +41,13 @@
android::sp<android::apex::IApexService> GetApexService() const;
};
+class FlattenedApexHandlerAndroid : virtual public ApexHandlerInterface {
+ public:
+ android::base::Result<uint64_t> CalculateSize(
+ const std::vector<ApexInfo>& apex_infos) const;
+ bool AllocateSpace(const std::vector<ApexInfo>& apex_infos) const;
+};
+
} // namespace chromeos_update_engine
#endif // SYSTEM_UPDATE_ENGINE_AOSP_APEX_HANDLER_ANDROID_H_
diff --git a/aosp/apex_handler_android_unittest.cc b/aosp/apex_handler_android_unittest.cc
index 981ae9d..847ccaa 100644
--- a/aosp/apex_handler_android_unittest.cc
+++ b/aosp/apex_handler_android_unittest.cc
@@ -41,7 +41,7 @@
return std::move(result);
}
-TEST(ApexHandlerAndroidTest, CalculateSize) {
+TEST(ApexHandlerAndroidTest, CalculateSizeUpdatableApex) {
ApexHandlerAndroid apex_handler;
std::vector<ApexInfo> apex_infos;
ApexInfo compressed_apex_1 = CreateApexInfo("sample1", 1, true, 1);
@@ -52,10 +52,10 @@
apex_infos.push_back(uncompressed_apex);
auto result = apex_handler.CalculateSize(apex_infos);
ASSERT_TRUE(result.ok());
- EXPECT_EQ(*result, 3u);
+ ASSERT_EQ(*result, 3u);
}
-TEST(ApexHandlerAndroidTest, AllocateSpace) {
+TEST(ApexHandlerAndroidTest, AllocateSpaceUpdatableApex) {
ApexHandlerAndroid apex_handler;
std::vector<ApexInfo> apex_infos;
ApexInfo compressed_apex_1 = CreateApexInfo("sample1", 1, true, 1);
@@ -64,10 +64,39 @@
apex_infos.push_back(compressed_apex_1);
apex_infos.push_back(compressed_apex_2);
apex_infos.push_back(uncompressed_apex);
- EXPECT_TRUE(apex_handler.AllocateSpace(apex_infos));
+ ASSERT_TRUE(apex_handler.AllocateSpace(apex_infos));
// Should be able to pass empty list
- EXPECT_TRUE(apex_handler.AllocateSpace({}));
+ ASSERT_TRUE(apex_handler.AllocateSpace({}));
+}
+
+TEST(ApexHandlerAndroidTest, CalculateSizeFlattenedApex) {
+ FlattenedApexHandlerAndroid apex_handler;
+ std::vector<ApexInfo> apex_infos;
+ ApexInfo compressed_apex_1 = CreateApexInfo("sample1", 1, true, 1);
+ ApexInfo compressed_apex_2 = CreateApexInfo("sample2", 2, true, 2);
+ ApexInfo uncompressed_apex = CreateApexInfo("uncompressed", 1, false, 4);
+ apex_infos.push_back(compressed_apex_1);
+ apex_infos.push_back(compressed_apex_2);
+ apex_infos.push_back(uncompressed_apex);
+ auto result = apex_handler.CalculateSize(apex_infos);
+ ASSERT_TRUE(result.ok());
+ ASSERT_EQ(*result, 0u);
+}
+
+TEST(ApexHandlerAndroidTest, AllocateSpaceFlattenedApex) {
+ FlattenedApexHandlerAndroid apex_handler;
+ std::vector<ApexInfo> apex_infos;
+ ApexInfo compressed_apex_1 = CreateApexInfo("sample1", 1, true, 1);
+ ApexInfo compressed_apex_2 = CreateApexInfo("sample2", 2, true, 2);
+ ApexInfo uncompressed_apex = CreateApexInfo("uncompressed", 1, false, 4);
+ apex_infos.push_back(compressed_apex_1);
+ apex_infos.push_back(compressed_apex_2);
+ apex_infos.push_back(uncompressed_apex);
+ ASSERT_TRUE(apex_handler.AllocateSpace(apex_infos));
+
+ // Should be able to pass empty list
+ ASSERT_TRUE(apex_handler.AllocateSpace({}));
}
} // namespace chromeos_update_engine
diff --git a/aosp/cleanup_previous_update_action.cc b/aosp/cleanup_previous_update_action.cc
index 68954f6..51bb083 100644
--- a/aosp/cleanup_previous_update_action.cc
+++ b/aosp/cleanup_previous_update_action.cc
@@ -276,7 +276,17 @@
void CleanupPreviousUpdateAction::WaitForMergeOrSchedule() {
AcknowledgeTaskExecuted();
TEST_AND_RETURN(running_);
+
auto update_uses_compression = snapshot_->UpdateUsesCompression();
+
+ // Propagate the merge failure code to the merge stats. If we wait until
+ // after ProcessUpdateState, then a successful merge could overwrite the
+ // state of the previous failure.
+ auto failure_code = snapshot_->ReadMergeFailureCode();
+ if (failure_code != android::snapshot::MergeFailureCode::Ok) {
+ merge_stats_->set_merge_failure_code(failure_code);
+ }
+
auto state = snapshot_->ProcessUpdateState(
std::bind(&CleanupPreviousUpdateAction::OnMergePercentageUpdate, this),
std::bind(&CleanupPreviousUpdateAction::BeforeCancel, this));
@@ -324,6 +334,7 @@
case UpdateState::MergeFailed: {
LOG(ERROR) << "Merge failed. Device may be corrupted.";
+ merge_stats_->set_merge_failure_code(snapshot_->ReadMergeFailureCode());
processor_->ActionComplete(this, ErrorCode::kDeviceCorrupted);
return;
}
@@ -492,7 +503,8 @@
report.total_cow_size_bytes(),
report.estimated_cow_size_bytes(),
report.boot_complete_time_ms(),
- report.boot_complete_to_merge_start_time_ms());
+ report.boot_complete_to_merge_start_time_ms(),
+ static_cast<int32_t>(report.merge_failure_code()));
#endif
}
diff --git a/aosp/daemon_state_android.cc b/aosp/daemon_state_android.cc
index fc89d73..da49080 100644
--- a/aosp/daemon_state_android.cc
+++ b/aosp/daemon_state_android.cc
@@ -65,12 +65,11 @@
certificate_checker_->Init();
// Initialize the UpdateAttempter before the UpdateManager.
- update_attempter_.reset(
- new UpdateAttempterAndroid(this,
- prefs_.get(),
- boot_control_.get(),
- hardware_.get(),
- std::make_unique<ApexHandlerAndroid>()));
+ update_attempter_.reset(new UpdateAttempterAndroid(this,
+ prefs_.get(),
+ boot_control_.get(),
+ hardware_.get(),
+ CreateApexHandler()));
return true;
}
diff --git a/aosp/dynamic_partition_control_android.cc b/aosp/dynamic_partition_control_android.cc
index 444fe42..538b57c 100644
--- a/aosp/dynamic_partition_control_android.cc
+++ b/aosp/dynamic_partition_control_android.cc
@@ -32,6 +32,7 @@
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
#include <bootloader_message/bootloader_message.h>
#include <fs_mgr.h>
#include <fs_mgr_dm_linear.h>
@@ -70,6 +71,7 @@
using android::snapshot::SnapshotManager;
using android::snapshot::SnapshotManagerStub;
using android::snapshot::UpdateState;
+using base::StringPrintf;
namespace chromeos_update_engine {
@@ -830,33 +832,90 @@
return StoreMetadata(target_device, builder.get(), target_slot);
}
+DynamicPartitionControlAndroid::SpaceLimit
+DynamicPartitionControlAndroid::GetSpaceLimit(bool use_snapshot) {
+ // On device retrofitting dynamic partitions, allocatable_space = "super",
+ // where "super" is the sum of all block devices for that slot. Since block
+ // devices are dedicated for the corresponding slot, there's no need to halve
+ // the allocatable space.
+ if (GetDynamicPartitionsFeatureFlag().IsRetrofit())
+ return SpaceLimit::ERROR_IF_EXCEEDED_SUPER;
+
+ // On device launching dynamic partitions w/o VAB, regardless of recovery
+ // sideload, super partition must be big enough to hold both A and B slots of
+ // groups. Hence,
+ // allocatable_space = super / 2
+ if (!GetVirtualAbFeatureFlag().IsEnabled())
+ return SpaceLimit::ERROR_IF_EXCEEDED_HALF_OF_SUPER;
+
+ // Source build supports VAB. Super partition must be big enough to hold
+ // one slot of groups (ERROR_IF_EXCEEDED_SUPER). However, there are cases
+ // where additional warning messages needs to be written.
+
+ // If using snapshot updates, implying that target build also uses VAB,
+ // allocatable_space = super
+ if (use_snapshot)
+ return SpaceLimit::ERROR_IF_EXCEEDED_SUPER;
+
+ // Source build supports VAB but not using snapshot updates. There are
+ // several cases, as listed below.
+ // Sideloading: allocatable_space = super.
+ if (IsRecovery())
+ return SpaceLimit::ERROR_IF_EXCEEDED_SUPER;
+
+ // On launch VAB device, this implies secondary payload.
+ // Technically, we don't have to check anything, but sum(groups) < super
+ // still applies.
+ if (!GetVirtualAbFeatureFlag().IsRetrofit())
+ return SpaceLimit::ERROR_IF_EXCEEDED_SUPER;
+
+ // On retrofit VAB device, either of the following:
+ // - downgrading: allocatable_space = super / 2
+ // - secondary payload: don't check anything
+ // These two cases are indistinguishable,
+ // hence emit warning if sum(groups) > super / 2
+ return SpaceLimit::WARN_IF_EXCEEDED_HALF_OF_SUPER;
+}
+
bool DynamicPartitionControlAndroid::CheckSuperPartitionAllocatableSpace(
android::fs_mgr::MetadataBuilder* builder,
const DeltaArchiveManifest& manifest,
bool use_snapshot) {
- uint64_t total_size = 0;
+ uint64_t sum_groups = 0;
for (const auto& group : manifest.dynamic_partition_metadata().groups()) {
- total_size += group.size();
+ sum_groups += group.size();
}
- std::string expr;
- uint64_t allocatable_space = builder->AllocatableSpace();
- // On device retrofitting dynamic partitions, allocatable_space = super.
- // On device launching dynamic partitions w/o VAB,
- // allocatable_space = super / 2.
- // On device launching dynamic partitions with VAB, allocatable_space = super.
- // For recovery sideload, allocatable_space = super.
- if (!GetDynamicPartitionsFeatureFlag().IsRetrofit() && !use_snapshot &&
- !IsRecovery()) {
- allocatable_space /= 2;
- expr = "half of ";
- }
- if (total_size > allocatable_space) {
- LOG(ERROR) << "The maximum size of all groups for the target slot"
- << " (" << total_size << ") has exceeded " << expr
- << "allocatable space for dynamic partitions "
- << allocatable_space << ".";
- return false;
+ uint64_t full_space = builder->AllocatableSpace();
+ uint64_t half_space = full_space / 2;
+ constexpr const char* fmt =
+ "The maximum size of all groups for the target slot (%" PRIu64
+ ") has exceeded %sallocatable space for dynamic partitions %" PRIu64 ".";
+ switch (GetSpaceLimit(use_snapshot)) {
+ case SpaceLimit::ERROR_IF_EXCEEDED_HALF_OF_SUPER: {
+ if (sum_groups > half_space) {
+ LOG(ERROR) << StringPrintf(fmt, sum_groups, "HALF OF ", half_space);
+ return false;
+ }
+ // If test passes, it implies that the following two conditions also pass.
+ break;
+ }
+ case SpaceLimit::WARN_IF_EXCEEDED_HALF_OF_SUPER: {
+ if (sum_groups > half_space) {
+ LOG(WARNING) << StringPrintf(fmt, sum_groups, "HALF OF ", half_space)
+ << " This is allowed for downgrade or secondary OTA on "
+ "retrofit VAB device.";
+ }
+ // still check sum(groups) < super
+ [[fallthrough]];
+ }
+ case SpaceLimit::ERROR_IF_EXCEEDED_SUPER: {
+ if (sum_groups > full_space) {
+ LOG(ERROR) << base::StringPrintf(fmt, sum_groups, "", full_space);
+ return false;
+ }
+ break;
+ }
}
return true;
@@ -910,9 +969,16 @@
uint32_t target_slot,
const DeltaArchiveManifest& manifest) {
// Check preconditions.
- LOG_IF(WARNING, !GetVirtualAbFeatureFlag().IsEnabled() || IsRecovery())
- << "UpdatePartitionMetadata is called on a Virtual A/B device "
- "but source partitions is not deleted. This is not allowed.";
+ if (GetVirtualAbFeatureFlag().IsEnabled()) {
+ CHECK(!target_supports_snapshot_ || IsRecovery())
+ << "Must use snapshot on VAB device when target build supports VAB and "
+ "not sideloading.";
+ LOG_IF(INFO, !target_supports_snapshot_)
+ << "Not using snapshot on VAB device because target build does not "
+ "support snapshot. Secondary or downgrade OTA?";
+ LOG_IF(INFO, IsRecovery())
+ << "Not using snapshot on VAB device because sideloading.";
+ }
// If applying downgrade from Virtual A/B to non-Virtual A/B, the left-over
// COW group needs to be deleted to ensure there are enough space to create
@@ -1050,9 +1116,9 @@
if (UpdateUsesSnapshotCompression() && slot != current_slot &&
IsDynamicPartition(partition_name, slot)) {
return {
- {.mountable_device_path = base::FilePath{std::string{VABC_DEVICE_DIR}}
- .Append(partition_name_suffix)
- .value(),
+ {.readonly_device_path = base::FilePath{std::string{VABC_DEVICE_DIR}}
+ .Append(partition_name_suffix)
+ .value(),
.is_dynamic = true}};
}
@@ -1071,7 +1137,7 @@
&device)) {
case DynamicPartitionDeviceStatus::SUCCESS:
return {{.rw_device_path = device,
- .mountable_device_path = device,
+ .readonly_device_path = device,
.is_dynamic = true}};
case DynamicPartitionDeviceStatus::TRY_STATIC:
@@ -1089,7 +1155,7 @@
}
return {{.rw_device_path = static_path,
- .mountable_device_path = static_path,
+ .readonly_device_path = static_path,
.is_dynamic = false}};
}
@@ -1404,7 +1470,8 @@
}
bool DynamicPartitionControlAndroid::UpdateUsesSnapshotCompression() {
- return snapshot_->UpdateUsesCompression();
+ return GetVirtualAbFeatureFlag().IsEnabled() &&
+ snapshot_->UpdateUsesCompression();
}
} // namespace chromeos_update_engine
diff --git a/aosp/dynamic_partition_control_android.h b/aosp/dynamic_partition_control_android.h
index b7aa7ea..df91401 100644
--- a/aosp/dynamic_partition_control_android.h
+++ b/aosp/dynamic_partition_control_android.h
@@ -258,6 +258,18 @@
const DeltaArchiveManifest& manifest,
uint64_t* required_size);
+ enum SpaceLimit {
+ // Most restricted: if sum(groups) > super / 2, error
+ ERROR_IF_EXCEEDED_HALF_OF_SUPER,
+ // Implies ERROR_IF_EXCEEDED_SUPER; then, if sum(groups) > super / 2, warn
+ WARN_IF_EXCEEDED_HALF_OF_SUPER,
+ // Least restricted: if sum(groups) > super, error
+ ERROR_IF_EXCEEDED_SUPER,
+ };
+ // Helper of CheckSuperPartitionAllocatableSpace. Determine limit for groups
+ // and partitions.
+ SpaceLimit GetSpaceLimit(bool use_snapshot);
+
// Returns true if the allocatable space in super partition is larger than
// the size of dynamic partition groups in the manifest.
bool CheckSuperPartitionAllocatableSpace(
diff --git a/aosp/dynamic_partition_control_android_unittest.cc b/aosp/dynamic_partition_control_android_unittest.cc
index 0bb8df7..6f1d4ef 100644
--- a/aosp/dynamic_partition_control_android_unittest.cc
+++ b/aosp/dynamic_partition_control_android_unittest.cc
@@ -431,7 +431,7 @@
auto device_info =
dynamicControl().GetPartitionDevice("system", target(), source(), false);
ASSERT_TRUE(device_info.has_value());
- ASSERT_EQ(device_info->mountable_device_path, device);
+ ASSERT_EQ(device_info->readonly_device_path, device);
}
TEST_P(DynamicPartitionControlAndroidTestP, GetMountableDevicePathVABC) {
@@ -475,7 +475,7 @@
ASSERT_TRUE(device_info.has_value());
base::FilePath vabc_device_dir{
std::string{DynamicPartitionControlAndroid::VABC_DEVICE_DIR}};
- ASSERT_EQ(device_info->mountable_device_path,
+ ASSERT_EQ(device_info->readonly_device_path,
vabc_device_dir.Append(T("system")).value());
}
diff --git a/aosp/update_attempter_android.cc b/aosp/update_attempter_android.cc
index ba61f25..c1e15c0 100644
--- a/aosp/update_attempter_android.cc
+++ b/aosp/update_attempter_android.cc
@@ -159,7 +159,7 @@
SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
} else {
SetStatusAndNotify(UpdateStatus::IDLE);
- UpdatePrefsAndReportUpdateMetricsOnReboot();
+ UpdateStateAfterReboot();
#ifdef _UE_SIDELOAD
LOG(INFO) << "Skip ScheduleCleanupPreviousUpdate in sideload because "
<< "ApplyPayload will call it later.";
@@ -364,6 +364,12 @@
LOG(INFO) << "Attempting to reset state from "
<< UpdateStatusToString(status_) << " to UpdateStatus::IDLE";
+ if (apex_handler_android_ != nullptr) {
+ LOG(INFO) << "Cleaning up reserved space for compressed APEX (if any)";
+ std::vector<ApexInfo> apex_infos_blank;
+ apex_handler_android_->AllocateSpace(apex_infos_blank);
+ }
+
switch (status_) {
case UpdateStatus::IDLE: {
if (!boot_control_->GetDynamicPartitionControl()->ResetUpdate(prefs_)) {
@@ -877,27 +883,32 @@
}
}
-void UpdateAttempterAndroid::UpdatePrefsAndReportUpdateMetricsOnReboot() {
+void UpdateAttempterAndroid::UpdateStateAfterReboot() {
string current_boot_id;
TEST_AND_RETURN(utils::GetBootId(¤t_boot_id));
// Example: [ro.build.version.incremental]: [4292972]
string current_version =
android::base::GetProperty("ro.build.version.incremental", "");
TEST_AND_RETURN(!current_version.empty());
+ const auto current_slot = boot_control_->GetCurrentSlot();
// If there's no record of previous version (e.g. due to a data wipe), we
// save the info of current boot and skip the metrics report.
if (!prefs_->Exists(kPrefsPreviousVersion)) {
prefs_->SetString(kPrefsBootId, current_boot_id);
prefs_->SetString(kPrefsPreviousVersion, current_version);
+ prefs_->SetInt64(std::string{kPrefsPreviousSlot},
+ boot_control_->GetCurrentSlot());
ClearMetricsPrefs();
return;
}
+ int64_t previous_slot = -1;
+ prefs_->GetInt64(kPrefsPreviousSlot, &previous_slot);
string previous_version;
- // update_engine restarted under the same build.
+ // update_engine restarted under the same build and same slot.
// TODO(xunchang) identify and report rollback by checking UpdateMarker.
if (prefs_->GetString(kPrefsPreviousVersion, &previous_version) &&
- previous_version == current_version) {
+ previous_version == current_version && previous_slot == current_slot) {
string last_boot_id;
bool is_reboot = prefs_->Exists(kPrefsBootId) &&
(prefs_->GetString(kPrefsBootId, &last_boot_id) &&
@@ -917,6 +928,8 @@
// TODO(xunchang) check the build version is larger than the previous one.
prefs_->SetString(kPrefsBootId, current_boot_id);
prefs_->SetString(kPrefsPreviousVersion, current_version);
+ prefs_->SetInt64(std::string{kPrefsPreviousSlot},
+ boot_control_->GetCurrentSlot());
bool previous_attempt_exists = prefs_->Exists(kPrefsPayloadAttemptNumber);
// |kPrefsPayloadAttemptNumber| should be cleared upon successful update.
diff --git a/aosp/update_attempter_android.h b/aosp/update_attempter_android.h
index 70938bc..7a5a635 100644
--- a/aosp/update_attempter_android.h
+++ b/aosp/update_attempter_android.h
@@ -162,12 +162,16 @@
// |kPrefsSystemUpdatedMarker|
void CollectAndReportUpdateMetricsOnUpdateFinished(ErrorCode error_code);
+ // This function is called after update_engine is started after device
+ // reboots. If update_engine is restarted w/o device reboot, this function
+ // would not be called.
+
// Metrics report function to call:
// |ReportAbnormallyTerminatedUpdateAttemptMetrics|
// |ReportTimeToRebootMetrics|
// Prefs to update:
// |kPrefsBootId|, |kPrefsPreviousVersion|
- void UpdatePrefsAndReportUpdateMetricsOnReboot();
+ void UpdateStateAfterReboot();
// Prefs to update:
// |kPrefsPayloadAttemptNumber|, |kPrefsUpdateTimestampStart|,
diff --git a/aosp/update_attempter_android_unittest.cc b/aosp/update_attempter_android_unittest.cc
index f799df3..f73df16 100644
--- a/aosp/update_attempter_android_unittest.cc
+++ b/aosp/update_attempter_android_unittest.cc
@@ -24,6 +24,7 @@
#include <base/time/time.h>
#include <gtest/gtest.h>
+#include "common/constants.h"
#include "update_engine/aosp/daemon_state_android.h"
#include "update_engine/common/fake_boot_control.h"
#include "update_engine/common/fake_clock.h"
@@ -81,6 +82,8 @@
prefs_.SetString(kPrefsPreviousVersion, build_version);
prefs_.SetString(kPrefsBootId, "oldboot");
prefs_.SetInt64(kPrefsNumReboots, 1);
+ prefs_.SetInt64(kPrefsPreviousSlot, 1);
+ boot_control_.SetCurrentSlot(1);
EXPECT_CALL(*metrics_reporter_, ReportTimeToReboot(_)).Times(0);
update_attempter_android_.Init();
@@ -88,15 +91,15 @@
// Check that the boot_id and reboot_count are updated.
std::string boot_id;
utils::GetBootId(&boot_id);
- EXPECT_TRUE(prefs_.Exists(kPrefsBootId));
+ ASSERT_TRUE(prefs_.Exists(kPrefsBootId));
std::string prefs_boot_id;
- EXPECT_TRUE(prefs_.GetString(kPrefsBootId, &prefs_boot_id));
- EXPECT_EQ(boot_id, prefs_boot_id);
+ ASSERT_TRUE(prefs_.GetString(kPrefsBootId, &prefs_boot_id));
+ ASSERT_EQ(boot_id, prefs_boot_id);
- EXPECT_TRUE(prefs_.Exists(kPrefsNumReboots));
+ ASSERT_TRUE(prefs_.Exists(kPrefsNumReboots));
int64_t reboot_count;
- EXPECT_TRUE(prefs_.GetInt64(kPrefsNumReboots, &reboot_count));
- EXPECT_EQ(2, reboot_count);
+ ASSERT_TRUE(prefs_.GetInt64(kPrefsNumReboots, &reboot_count));
+ ASSERT_EQ(2, reboot_count);
}
TEST_F(UpdateAttempterAndroidTest, UpdatePrefsBuildVersionChangeOnInit) {
diff --git a/common/constants.cc b/common/constants.cc
index a9cf238..ff46755 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -16,132 +16,4 @@
#include "update_engine/common/constants.h"
-namespace chromeos_update_engine {
-
-const char kExclusionPrefsSubDir[] = "exclusion";
-
-const char kDlcPrefsSubDir[] = "dlc";
-
-const char kPowerwashSafePrefsSubDirectory[] = "update_engine/prefs";
-
-const char kPrefsSubDirectory[] = "prefs";
-
-const char kStatefulPartition[] = "/mnt/stateful_partition";
-
-const char kPostinstallDefaultScript[] = "postinst";
-
-// Constants defining keys for the persisted state of update engine.
-const char kPrefsAttemptInProgress[] = "attempt-in-progress";
-const char kPrefsBackoffExpiryTime[] = "backoff-expiry-time";
-const char kPrefsBootId[] = "boot-id";
-const char kPrefsCurrentBytesDownloaded[] = "current-bytes-downloaded";
-const char kPrefsCurrentResponseSignature[] = "current-response-signature";
-const char kPrefsCurrentUrlFailureCount[] = "current-url-failure-count";
-const char kPrefsCurrentUrlIndex[] = "current-url-index";
-const char kPrefsDailyMetricsLastReportedAt[] =
- "daily-metrics-last-reported-at";
-const char kPrefsDeltaUpdateFailures[] = "delta-update-failures";
-const char kPrefsDynamicPartitionMetadataUpdated[] =
- "dynamic-partition-metadata-updated";
-const char kPrefsFullPayloadAttemptNumber[] = "full-payload-attempt-number";
-const char kPrefsInstallDateDays[] = "install-date-days";
-const char kPrefsLastActivePingDay[] = "last-active-ping-day";
-const char kPrefsLastRollCallPingDay[] = "last-roll-call-ping-day";
-const char kPrefsManifestMetadataSize[] = "manifest-metadata-size";
-const char kPrefsManifestSignatureSize[] = "manifest-signature-size";
-const char kPrefsMetricsAttemptLastReportingTime[] =
- "metrics-attempt-last-reporting-time";
-const char kPrefsMetricsCheckLastReportingTime[] =
- "metrics-check-last-reporting-time";
-const char kPrefsNoIgnoreBackoff[] = "no-ignore-backoff";
-const char kPrefsNumReboots[] = "num-reboots";
-const char kPrefsNumResponsesSeen[] = "num-responses-seen";
-const char kPrefsOmahaCohort[] = "omaha-cohort";
-const char kPrefsOmahaCohortHint[] = "omaha-cohort-hint";
-const char kPrefsOmahaCohortName[] = "omaha-cohort-name";
-const char kPrefsOmahaEolDate[] = "omaha-eol-date";
-const char kPrefsP2PEnabled[] = "p2p-enabled";
-const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
-const char kPrefsP2PNumAttempts[] = "p2p-num-attempts";
-const char kPrefsPayloadAttemptNumber[] = "payload-attempt-number";
-const char kPrefsTestUpdateCheckIntervalTimeout[] =
- "test-update-check-interval-timeout";
-// Keep |kPrefsPingActive| in sync with |kDlcMetadataFilePingActive| in
-// dlcservice.
-const char kPrefsPingActive[] = "active";
-const char kPrefsPingLastActive[] = "date_last_active";
-const char kPrefsPingLastRollcall[] = "date_last_rollcall";
-const char kPrefsLastFp[] = "last-fp";
-const char kPrefsPostInstallSucceeded[] = "post-install-succeeded";
-const char kPrefsPreviousVersion[] = "previous-version";
-const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
-const char kPrefsRollbackHappened[] = "rollback-happened";
-const char kPrefsRollbackVersion[] = "rollback-version";
-const char kPrefsChannelOnSlotPrefix[] = "channel-on-slot-";
-const char kPrefsSystemUpdatedMarker[] = "system-updated-marker";
-const char kPrefsTargetVersionAttempt[] = "target-version-attempt";
-const char kPrefsTargetVersionInstalledFrom[] = "target-version-installed-from";
-const char kPrefsTargetVersionUniqueId[] = "target-version-unique-id";
-const char kPrefsTotalBytesDownloaded[] = "total-bytes-downloaded";
-const char kPrefsUpdateCheckCount[] = "update-check-count";
-const char kPrefsUpdateCheckResponseHash[] = "update-check-response-hash";
-const char kPrefsUpdateCompletedBootTime[] = "update-completed-boot-time";
-const char kPrefsUpdateCompletedOnBootId[] = "update-completed-on-boot-id";
-const char kPrefsUpdateDurationUptime[] = "update-duration-uptime";
-const char kPrefsUpdateFirstSeenAt[] = "update-first-seen-at";
-const char kPrefsUpdateOverCellularPermission[] =
- "update-over-cellular-permission";
-const char kPrefsUpdateOverCellularTargetVersion[] =
- "update-over-cellular-target-version";
-const char kPrefsUpdateOverCellularTargetSize[] =
- "update-over-cellular-target-size";
-const char kPrefsUpdateServerCertificate[] = "update-server-cert";
-const char kPrefsUpdateStateNextDataLength[] = "update-state-next-data-length";
-const char kPrefsUpdateStateNextDataOffset[] = "update-state-next-data-offset";
-const char kPrefsUpdateStateNextOperation[] = "update-state-next-operation";
-const char kPrefsUpdateStatePayloadIndex[] = "update-state-payload-index";
-const char kPrefsUpdateStateSHA256Context[] = "update-state-sha-256-context";
-const char kPrefsUpdateStateSignatureBlob[] = "update-state-signature-blob";
-const char kPrefsUpdateStateSignedSHA256Context[] =
- "update-state-signed-sha-256-context";
-const char kPrefsUpdateBootTimestampStart[] = "update-boot-timestamp-start";
-const char kPrefsUpdateTimestampStart[] = "update-timestamp-start";
-const char kPrefsUrlSwitchCount[] = "url-switch-count";
-const char kPrefsVerityWritten[] = "verity-written";
-const char kPrefsWallClockScatteringWaitPeriod[] = "wall-clock-wait-period";
-const char kPrefsWallClockStagingWaitPeriod[] =
- "wall-clock-staging-wait-period";
-const char kPrefsManifestBytes[] = "manifest-bytes";
-
-// These four fields are generated by scripts/brillo_update_payload.
-const char kPayloadPropertyFileSize[] = "FILE_SIZE";
-const char kPayloadPropertyFileHash[] = "FILE_HASH";
-const char kPayloadPropertyMetadataSize[] = "METADATA_SIZE";
-const char kPayloadPropertyMetadataHash[] = "METADATA_HASH";
-// The Authorization: HTTP header to be sent when downloading the payload.
-const char kPayloadPropertyAuthorization[] = "AUTHORIZATION";
-// The User-Agent HTTP header to be sent when downloading the payload.
-const char kPayloadPropertyUserAgent[] = "USER_AGENT";
-// Set "POWERWASH=1" to powerwash (factory data reset) the device after
-// applying the update.
-const char kPayloadPropertyPowerwash[] = "POWERWASH";
-// The network id to pass to android_setprocnetwork before downloading.
-// This can be used to zero-rate OTA traffic by sending it over the correct
-// network.
-const char kPayloadPropertyNetworkId[] = "NETWORK_ID";
-// Set "SWITCH_SLOT_ON_REBOOT=0" to skip marking the updated partitions active.
-// The default is 1 (always switch slot if update succeeded).
-const char kPayloadPropertySwitchSlotOnReboot[] = "SWITCH_SLOT_ON_REBOOT";
-// Set "RUN_POST_INSTALL=0" to skip running optional post install.
-// The default is 1 (always run post install).
-const char kPayloadPropertyRunPostInstall[] = "RUN_POST_INSTALL";
-
-const char kOmahaUpdaterVersion[] = "0.1.0.0";
-
-// X-Goog-Update headers.
-const char kXGoogleUpdateInteractivity[] = "X-Goog-Update-Interactivity";
-const char kXGoogleUpdateAppId[] = "X-Goog-Update-AppId";
-const char kXGoogleUpdateUpdater[] = "X-Goog-Update-Updater";
-const char kXGoogleUpdateSessionId[] = "X-Goog-SessionId";
-
-} // namespace chromeos_update_engine
+namespace chromeos_update_engine {} // namespace chromeos_update_engine
diff --git a/common/constants.h b/common/constants.h
index 64447ce..8c07fcf 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -20,115 +20,176 @@
#include <cstdint>
namespace chromeos_update_engine {
-
// The root path of all exclusion prefs.
-extern const char kExclusionPrefsSubDir[];
+static constexpr const auto& kExclusionPrefsSubDir = "exclusion";
// The root path of all DLC metadata.
-extern const char kDlcPrefsSubDir[];
+static constexpr const auto& kDlcPrefsSubDir = "dlc";
// Directory for AU prefs that are preserved across powerwash.
-extern const char kPowerwashSafePrefsSubDirectory[];
+static constexpr const auto& kPowerwashSafePrefsSubDirectory =
+ "update_engine/prefs";
// The location where we store the AU preferences (state etc).
-extern const char kPrefsSubDirectory[];
-
-// Path to the post install command, relative to the partition.
-extern const char kPostinstallDefaultScript[];
+static constexpr const auto& kPrefsSubDirectory = "prefs";
// Path to the stateful partition on the root filesystem.
-extern const char kStatefulPartition[];
+static constexpr const auto& kStatefulPartition = "/mnt/stateful_partition";
+
+// Path to the post install command, relative to the partition.
+static constexpr const auto& kPostinstallDefaultScript = "postinst";
// Constants related to preferences.
-extern const char kPrefsAttemptInProgress[];
-extern const char kPrefsBackoffExpiryTime[];
-extern const char kPrefsBootId[];
-extern const char kPrefsCurrentBytesDownloaded[];
-extern const char kPrefsCurrentResponseSignature[];
-extern const char kPrefsCurrentUrlFailureCount[];
-extern const char kPrefsCurrentUrlIndex[];
-extern const char kPrefsDailyMetricsLastReportedAt[];
-extern const char kPrefsDeltaUpdateFailures[];
-extern const char kPrefsDynamicPartitionMetadataUpdated[];
-extern const char kPrefsFullPayloadAttemptNumber[];
-extern const char kPrefsInstallDateDays[];
-extern const char kPrefsLastActivePingDay[];
-extern const char kPrefsLastRollCallPingDay[];
-extern const char kPrefsManifestMetadataSize[];
-extern const char kPrefsManifestSignatureSize[];
-extern const char kPrefsMetricsAttemptLastReportingTime[];
-extern const char kPrefsMetricsCheckLastReportingTime[];
-extern const char kPrefsNoIgnoreBackoff[];
-extern const char kPrefsNumReboots[];
-extern const char kPrefsNumResponsesSeen[];
-extern const char kPrefsOmahaCohort[];
-extern const char kPrefsOmahaCohortHint[];
-extern const char kPrefsOmahaCohortName[];
-extern const char kPrefsOmahaEolDate[];
-extern const char kPrefsP2PEnabled[];
-extern const char kPrefsP2PFirstAttemptTimestamp[];
-extern const char kPrefsP2PNumAttempts[];
-extern const char kPrefsPayloadAttemptNumber[];
-extern const char kPrefsTestUpdateCheckIntervalTimeout[];
-extern const char kPrefsPingActive[];
-extern const char kPrefsPingLastActive[];
-extern const char kPrefsPingLastRollcall[];
-extern const char kPrefsLastFp[];
-extern const char kPrefsPostInstallSucceeded[];
-extern const char kPrefsPreviousVersion[];
-extern const char kPrefsResumedUpdateFailures[];
-extern const char kPrefsRollbackHappened[];
-extern const char kPrefsRollbackVersion[];
-extern const char kPrefsChannelOnSlotPrefix[];
-extern const char kPrefsSystemUpdatedMarker[];
-extern const char kPrefsTargetVersionAttempt[];
-extern const char kPrefsTargetVersionInstalledFrom[];
-extern const char kPrefsTargetVersionUniqueId[];
-extern const char kPrefsTotalBytesDownloaded[];
-extern const char kPrefsUpdateCheckCount[];
-extern const char kPrefsUpdateCheckResponseHash[];
-extern const char kPrefsUpdateCompletedBootTime[];
-extern const char kPrefsUpdateCompletedOnBootId[];
-extern const char kPrefsUpdateDurationUptime[];
-extern const char kPrefsUpdateFirstSeenAt[];
-extern const char kPrefsUpdateOverCellularPermission[];
-extern const char kPrefsUpdateOverCellularTargetVersion[];
-extern const char kPrefsUpdateOverCellularTargetSize[];
-extern const char kPrefsUpdateServerCertificate[];
-extern const char kPrefsUpdateStateNextDataLength[];
-extern const char kPrefsUpdateStateNextDataOffset[];
-extern const char kPrefsUpdateStateNextOperation[];
-extern const char kPrefsUpdateStatePayloadIndex[];
-extern const char kPrefsUpdateStateSHA256Context[];
-extern const char kPrefsUpdateStateSignatureBlob[];
-extern const char kPrefsUpdateStateSignedSHA256Context[];
-extern const char kPrefsUpdateBootTimestampStart[];
-extern const char kPrefsUpdateTimestampStart[];
-extern const char kPrefsUrlSwitchCount[];
-extern const char kPrefsVerityWritten[];
-extern const char kPrefsWallClockScatteringWaitPeriod[];
-extern const char kPrefsWallClockStagingWaitPeriod[];
-extern const char kPrefsManifestBytes[];
+// Constants defining keys for the persisted state of update engine.
+static constexpr const auto& kPrefsAttemptInProgress = "attempt-in-progress";
+static constexpr const auto& kPrefsBackoffExpiryTime = "backoff-expiry-time";
+static constexpr const auto& kPrefsBootId = "boot-id";
+static constexpr const auto& kPrefsCurrentBytesDownloaded =
+ "current-bytes-downloaded";
+static constexpr const auto& kPrefsCurrentResponseSignature =
+ "current-response-signature";
+static constexpr const auto& kPrefsCurrentUrlFailureCount =
+ "current-url-failure-count";
+static constexpr const auto& kPrefsCurrentUrlIndex = "current-url-index";
+static constexpr const auto& kPrefsDailyMetricsLastReportedAt =
+ "daily-metrics-last-reported-at";
+static constexpr const auto& kPrefsDeltaUpdateFailures =
+ "delta-update-failures";
+static constexpr const auto& kPrefsDynamicPartitionMetadataUpdated =
+ "dynamic-partition-metadata-updated";
+static constexpr const auto& kPrefsFullPayloadAttemptNumber =
+ "full-payload-attempt-number";
+static constexpr const auto& kPrefsInstallDateDays = "install-date-days";
+static constexpr const auto& kPrefsLastActivePingDay = "last-active-ping-day";
+static constexpr const auto& kPrefsLastRollCallPingDay =
+ "last-roll-call-ping-day";
+static constexpr const auto& kPrefsManifestMetadataSize =
+ "manifest-metadata-size";
+static constexpr const auto& kPrefsManifestSignatureSize =
+ "manifest-signature-size";
+static constexpr const auto& kPrefsMetricsAttemptLastReportingTime =
+ "metrics-attempt-last-reporting-time";
+static constexpr const auto& kPrefsMetricsCheckLastReportingTime =
+ "metrics-check-last-reporting-time";
+static constexpr const auto& kPrefsNoIgnoreBackoff = "no-ignore-backoff";
+static constexpr const auto& kPrefsNumReboots = "num-reboots";
+static constexpr const auto& kPrefsNumResponsesSeen = "num-responses-seen";
+static constexpr const auto& kPrefsOmahaCohort = "omaha-cohort";
+static constexpr const auto& kPrefsOmahaCohortHint = "omaha-cohort-hint";
+static constexpr const auto& kPrefsOmahaCohortName = "omaha-cohort-name";
+static constexpr const auto& kPrefsOmahaEolDate = "omaha-eol-date";
+static constexpr const auto& kPrefsP2PEnabled = "p2p-enabled";
+static constexpr const auto& kPrefsP2PFirstAttemptTimestamp =
+ "p2p-first-attempt-timestamp";
+static constexpr const auto& kPrefsP2PNumAttempts = "p2p-num-attempts";
+static constexpr const auto& kPrefsPayloadAttemptNumber =
+ "payload-attempt-number";
+static constexpr const auto& kPrefsTestUpdateCheckIntervalTimeout =
+ "test-update-check-interval-timeout";
+// Keep |kPrefsPingActive| in sync with |kDlcMetadataFilePingActive| in
+// dlcservice.
+static constexpr const auto& kPrefsPingActive = "active";
+static constexpr const auto& kPrefsPingLastActive = "date_last_active";
+static constexpr const auto& kPrefsPingLastRollcall = "date_last_rollcall";
+static constexpr const auto& kPrefsLastFp = "last-fp";
+static constexpr const auto& kPrefsPostInstallSucceeded =
+ "post-install-succeeded";
+static constexpr const auto& kPrefsPreviousVersion = "previous-version";
+static constexpr const auto& kPrefsResumedUpdateFailures =
+ "resumed-update-failures";
+static constexpr const auto& kPrefsRollbackHappened = "rollback-happened";
+static constexpr const auto& kPrefsRollbackVersion = "rollback-version";
+static constexpr const auto& kPrefsChannelOnSlotPrefix = "channel-on-slot-";
+static constexpr const auto& kPrefsSystemUpdatedMarker =
+ "system-updated-marker";
+static constexpr const auto& kPrefsTargetVersionAttempt =
+ "target-version-attempt";
+static constexpr const auto& kPrefsTargetVersionInstalledFrom =
+ "target-version-installed-from";
+static constexpr const auto& kPrefsTargetVersionUniqueId =
+ "target-version-unique-id";
+static constexpr const auto& kPrefsTotalBytesDownloaded =
+ "total-bytes-downloaded";
+static constexpr const auto& kPrefsUpdateCheckCount = "update-check-count";
+static constexpr const auto& kPrefsUpdateCheckResponseHash =
+ "update-check-response-hash";
+static constexpr const auto& kPrefsUpdateCompletedBootTime =
+ "update-completed-boot-time";
+static constexpr const auto& kPrefsUpdateCompletedOnBootId =
+ "update-completed-on-boot-id";
+static constexpr const auto& kPrefsUpdateDurationUptime =
+ "update-duration-uptime";
+static constexpr const auto& kPrefsUpdateFirstSeenAt = "update-first-seen-at";
+static constexpr const auto& kPrefsUpdateOverCellularPermission =
+ "update-over-cellular-permission";
+static constexpr const auto& kPrefsUpdateOverCellularTargetVersion =
+ "update-over-cellular-target-version";
+static constexpr const auto& kPrefsUpdateOverCellularTargetSize =
+ "update-over-cellular-target-size";
+static constexpr const auto& kPrefsUpdateServerCertificate =
+ "update-server-cert";
+static constexpr const auto& kPrefsUpdateStateNextDataLength =
+ "update-state-next-data-length";
+static constexpr const auto& kPrefsUpdateStateNextDataOffset =
+ "update-state-next-data-offset";
+static constexpr const auto& kPrefsUpdateStateNextOperation =
+ "update-state-next-operation";
+static constexpr const auto& kPrefsUpdateStatePayloadIndex =
+ "update-state-payload-index";
+static constexpr const auto& kPrefsUpdateStateSHA256Context =
+ "update-state-sha-256-context";
+static constexpr const auto& kPrefsUpdateStateSignatureBlob =
+ "update-state-signature-blob";
+static constexpr const auto& kPrefsUpdateStateSignedSHA256Context =
+ "update-state-signed-sha-256-context";
+static constexpr const auto& kPrefsUpdateBootTimestampStart =
+ "update-boot-timestamp-start";
+static constexpr const auto& kPrefsUpdateTimestampStart =
+ "update-timestamp-start";
+static constexpr const auto& kPrefsUrlSwitchCount = "url-switch-count";
+static constexpr const auto& kPrefsVerityWritten = "verity-written";
+static constexpr const auto& kPrefsWallClockScatteringWaitPeriod =
+ "wall-clock-wait-period";
+static constexpr const auto& kPrefsWallClockStagingWaitPeriod =
+ "wall-clock-staging-wait-period";
+static constexpr const auto& kPrefsManifestBytes = "manifest-bytes";
+static constexpr const auto& kPrefsPreviousSlot = "previous-slot";
// Keys used when storing and loading payload properties.
-extern const char kPayloadPropertyFileSize[];
-extern const char kPayloadPropertyFileHash[];
-extern const char kPayloadPropertyMetadataSize[];
-extern const char kPayloadPropertyMetadataHash[];
-extern const char kPayloadPropertyAuthorization[];
-extern const char kPayloadPropertyUserAgent[];
-extern const char kPayloadPropertyPowerwash[];
-extern const char kPayloadPropertyNetworkId[];
-extern const char kPayloadPropertySwitchSlotOnReboot[];
-extern const char kPayloadPropertyRunPostInstall[];
+// These four fields are generated by scripts/brillo_update_payload.
+static constexpr const auto& kPayloadPropertyFileSize = "FILE_SIZE";
+static constexpr const auto& kPayloadPropertyFileHash = "FILE_HASH";
+static constexpr const auto& kPayloadPropertyMetadataSize = "METADATA_SIZE";
+static constexpr const auto& kPayloadPropertyMetadataHash = "METADATA_HASH";
+// The Authorization: HTTP header to be sent when downloading the payload.
+static constexpr const auto& kPayloadPropertyAuthorization = "AUTHORIZATION";
+// The User-Agent HTTP header to be sent when downloading the payload.
+static constexpr const auto& kPayloadPropertyUserAgent = "USER_AGENT";
+// Set "POWERWASH=1" to powerwash (factory data reset) the device after
+// applying the update.
+static constexpr const auto& kPayloadPropertyPowerwash = "POWERWASH";
+// The network id to pass to android_setprocnetwork before downloading.
+// This can be used to zero-rate OTA traffic by sending it over the correct
+// network.
+static constexpr const auto& kPayloadPropertyNetworkId = "NETWORK_ID";
+// Set "SWITCH_SLOT_ON_REBOOT=0" to skip marking the updated partitions active.
+// The default is 1 (always switch slot if update succeeded).
+static constexpr const auto& kPayloadPropertySwitchSlotOnReboot =
+ "SWITCH_SLOT_ON_REBOOT";
+// Set "RUN_POST_INSTALL=0" to skip running optional post install.
+// The default is 1 (always run post install).
+static constexpr const auto& kPayloadPropertyRunPostInstall =
+ "RUN_POST_INSTALL";
-extern const char kOmahaUpdaterVersion[];
+static constexpr const auto& kOmahaUpdaterVersion = "0.1.0.0";
// X-Goog-Update headers.
-extern const char kXGoogleUpdateInteractivity[];
-extern const char kXGoogleUpdateAppId[];
-extern const char kXGoogleUpdateUpdater[];
-extern const char kXGoogleUpdateSessionId[];
+// X-Goog-Update headers.
+static constexpr const auto& kXGoogleUpdateInteractivity =
+ "X-Goog-Update-Interactivity";
+static constexpr const auto& kXGoogleUpdateAppId = "X-Goog-Update-AppId";
+static constexpr const auto& kXGoogleUpdateUpdater = "X-Goog-Update-Updater";
+static constexpr const auto& kXGoogleUpdateSessionId = "X-Goog-SessionId";
// A download source is any combination of protocol and server (that's of
// interest to us when looking at UMA metrics) using which we may download
diff --git a/common/dynamic_partition_control_interface.h b/common/dynamic_partition_control_interface.h
index d5e1d8d..a5be6e1 100644
--- a/common/dynamic_partition_control_interface.h
+++ b/common/dynamic_partition_control_interface.h
@@ -39,7 +39,7 @@
struct PartitionDevice {
std::string rw_device_path;
- std::string mountable_device_path;
+ std::string readonly_device_path;
bool is_dynamic;
};
diff --git a/common/fake_boot_control.h b/common/fake_boot_control.h
index fc7839d..79e2139 100644
--- a/common/fake_boot_control.h
+++ b/common/fake_boot_control.h
@@ -137,7 +137,7 @@
PartitionDevice device;
device.is_dynamic = false;
device.rw_device_path = device_path->second;
- device.mountable_device_path = device.rw_device_path;
+ device.readonly_device_path = device.rw_device_path;
return device;
}
diff --git a/common/fake_prefs.cc b/common/fake_prefs.cc
index ea6ea60..e87e0ec 100644
--- a/common/fake_prefs.cc
+++ b/common/fake_prefs.cc
@@ -28,7 +28,7 @@
namespace {
-void CheckNotNull(const string& key, void* ptr) {
+void CheckNotNull(std::string_view key, void* ptr) {
EXPECT_NE(nullptr, ptr) << "Called Get*() for key \"" << key
<< "\" with a null parameter.";
}
@@ -63,41 +63,41 @@
bool FakePrefs::PrefValue::*const FakePrefs::PrefConsts<bool>::member =
&FakePrefs::PrefValue::as_bool;
-bool FakePrefs::GetString(const string& key, string* value) const {
+bool FakePrefs::GetString(std::string_view key, string* value) const {
return GetValue(key, value);
}
-bool FakePrefs::SetString(const string& key, std::string_view value) {
+bool FakePrefs::SetString(std::string_view key, std::string_view value) {
SetValue(key, std::string(value));
return true;
}
-bool FakePrefs::GetInt64(const string& key, int64_t* value) const {
+bool FakePrefs::GetInt64(std::string_view key, int64_t* value) const {
return GetValue(key, value);
}
-bool FakePrefs::SetInt64(const string& key, const int64_t value) {
+bool FakePrefs::SetInt64(std::string_view key, const int64_t value) {
SetValue(key, value);
return true;
}
-bool FakePrefs::GetBoolean(const string& key, bool* value) const {
+bool FakePrefs::GetBoolean(std::string_view key, bool* value) const {
return GetValue(key, value);
}
-bool FakePrefs::SetBoolean(const string& key, const bool value) {
+bool FakePrefs::SetBoolean(std::string_view key, const bool value) {
SetValue(key, value);
return true;
}
-bool FakePrefs::Exists(const string& key) const {
+bool FakePrefs::Exists(std::string_view key) const {
return values_.find(key) != values_.end();
}
-bool FakePrefs::Delete(const string& key) {
+bool FakePrefs::Delete(std::string_view key) {
if (values_.find(key) == values_.end())
return false;
- values_.erase(key);
+ values_.erase(std::string{key});
const auto observers_for_key = observers_.find(key);
if (observers_for_key != observers_.end()) {
std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
@@ -107,7 +107,7 @@
return true;
}
-bool FakePrefs::Delete(const string& key, const vector<string>& nss) {
+bool FakePrefs::Delete(std::string_view key, const vector<string>& nss) {
bool success = Delete(key);
for (const auto& ns : nss) {
vector<string> ns_keys;
@@ -123,7 +123,7 @@
return success;
}
-bool FakePrefs::GetSubKeys(const string& ns, vector<string>* keys) const {
+bool FakePrefs::GetSubKeys(std::string_view ns, vector<string>* keys) const {
for (const auto& pr : values_)
if (pr.first.compare(0, ns.length(), ns) == 0)
keys->push_back(pr.first);
@@ -142,7 +142,7 @@
return "Unknown";
}
-void FakePrefs::CheckKeyType(const string& key, PrefType type) const {
+void FakePrefs::CheckKeyType(std::string_view key, PrefType type) const {
auto it = values_.find(key);
EXPECT_TRUE(it == values_.end() || it->second.type == type)
<< "Key \"" << key << "\" if defined as " << GetTypeName(it->second.type)
@@ -150,10 +150,11 @@
}
template <typename T>
-void FakePrefs::SetValue(const string& key, T value) {
+void FakePrefs::SetValue(std::string_view key, T value) {
+ std::string str_key{key};
CheckKeyType(key, PrefConsts<T>::type);
- values_[key].type = PrefConsts<T>::type;
- values_[key].value.*(PrefConsts<T>::member) = std::move(value);
+ values_[str_key].type = PrefConsts<T>::type;
+ values_[str_key].value.*(PrefConsts<T>::member) = std::move(value);
const auto observers_for_key = observers_.find(key);
if (observers_for_key != observers_.end()) {
std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
@@ -163,7 +164,7 @@
}
template <typename T>
-bool FakePrefs::GetValue(const string& key, T* value) const {
+bool FakePrefs::GetValue(std::string_view key, T* value) const {
CheckKeyType(key, PrefConsts<T>::type);
auto it = values_.find(key);
if (it == values_.end())
@@ -173,12 +174,14 @@
return true;
}
-void FakePrefs::AddObserver(const string& key, ObserverInterface* observer) {
- observers_[key].push_back(observer);
+void FakePrefs::AddObserver(std::string_view key, ObserverInterface* observer) {
+ observers_[string{key}].push_back(observer);
}
-void FakePrefs::RemoveObserver(const string& key, ObserverInterface* observer) {
- std::vector<ObserverInterface*>& observers_for_key = observers_[key];
+void FakePrefs::RemoveObserver(std::string_view key,
+ ObserverInterface* observer) {
+ string str_key{key};
+ std::vector<ObserverInterface*>& observers_for_key = observers_[str_key];
auto observer_it =
std::find(observers_for_key.begin(), observers_for_key.end(), observer);
EXPECT_NE(observer_it, observers_for_key.end())
@@ -186,7 +189,7 @@
if (observer_it != observers_for_key.end())
observers_for_key.erase(observer_it);
if (observers_for_key.empty())
- observers_.erase(key);
+ observers_.erase(str_key);
}
} // namespace chromeos_update_engine
diff --git a/common/fake_prefs.h b/common/fake_prefs.h
index 430c291..7ae9fb9 100644
--- a/common/fake_prefs.h
+++ b/common/fake_prefs.h
@@ -17,6 +17,7 @@
#ifndef UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
#define UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
+#include <functional>
#include <map>
#include <string>
#include <string_view>
@@ -40,24 +41,23 @@
~FakePrefs();
// PrefsInterface methods.
- bool GetString(const std::string& key, std::string* value) const override;
- bool SetString(const std::string& key, std::string_view value) override;
- bool GetInt64(const std::string& key, int64_t* value) const override;
- bool SetInt64(const std::string& key, const int64_t value) override;
- bool GetBoolean(const std::string& key, bool* value) const override;
- bool SetBoolean(const std::string& key, const bool value) override;
+ bool GetString(std::string_view key, std::string* value) const override;
+ bool SetString(std::string_view key, std::string_view value) override;
+ bool GetInt64(std::string_view key, int64_t* value) const override;
+ bool SetInt64(std::string_view key, const int64_t value) override;
+ bool GetBoolean(std::string_view key, bool* value) const override;
+ bool SetBoolean(std::string_view key, const bool value) override;
- bool Exists(const std::string& key) const override;
- bool Delete(const std::string& key) override;
- bool Delete(const std::string& key,
+ bool Exists(std::string_view key) const override;
+ bool Delete(std::string_view key) override;
+ bool Delete(std::string_view key,
const std::vector<std::string>& nss) override;
- bool GetSubKeys(const std::string& ns,
+ bool GetSubKeys(std::string_view ns,
std::vector<std::string>* keys) const override;
- void AddObserver(const std::string& key,
- ObserverInterface* observer) override;
- void RemoveObserver(const std::string& key,
+ void AddObserver(std::string_view key, ObserverInterface* observer) override;
+ void RemoveObserver(std::string_view key,
ObserverInterface* observer) override;
private:
@@ -92,24 +92,25 @@
static std::string GetTypeName(PrefType type);
// Checks that the |key| is either not present or has the given |type|.
- void CheckKeyType(const std::string& key, PrefType type) const;
+ void CheckKeyType(std::string_view key, PrefType type) const;
// Helper function to set a value of the passed |key|. It sets the type based
// on the template parameter T.
template <typename T>
- void SetValue(const std::string& key, T value);
+ void SetValue(std::string_view key, T value);
// Helper function to get a value from the map checking for invalid calls.
// The function fails the test if you attempt to read a value defined as a
// different type. Returns whether the get succeeded.
template <typename T>
- bool GetValue(const std::string& key, T* value) const;
+ bool GetValue(std::string_view key, T* value) const;
// Container for all the key/value pairs.
- std::map<std::string, PrefTypeValue> values_;
+ std::map<std::string, PrefTypeValue, std::less<>> values_;
// The registered observers watching for changes.
- std::map<std::string, std::vector<ObserverInterface*>> observers_;
+ std::map<std::string, std::vector<ObserverInterface*>, std::less<>>
+ observers_;
DISALLOW_COPY_AND_ASSIGN(FakePrefs);
};
diff --git a/common/hash_calculator.cc b/common/hash_calculator.cc
index d010a53..60812d5 100644
--- a/common/hash_calculator.cc
+++ b/common/hash_calculator.cc
@@ -95,6 +95,11 @@
return RawHashOfBytes(data.data(), data.size(), out_hash);
}
+bool HashCalculator::RawHashOfFile(const string& name, brillo::Blob* out_hash) {
+ const auto file_size = utils::FileSize(name);
+ return RawHashOfFile(name, file_size, out_hash) == file_size;
+}
+
off_t HashCalculator::RawHashOfFile(const string& name,
off_t length,
brillo::Blob* out_hash) {
diff --git a/common/hash_calculator.h b/common/hash_calculator.h
index b7e4d86..4426128 100644
--- a/common/hash_calculator.h
+++ b/common/hash_calculator.h
@@ -75,6 +75,7 @@
static off_t RawHashOfFile(const std::string& name,
off_t length,
brillo::Blob* out_hash);
+ static bool RawHashOfFile(const std::string& name, brillo::Blob* out_hash);
private:
// If non-empty, the final raw hash. Will only be set to non-empty when
diff --git a/common/mock_prefs.h b/common/mock_prefs.h
index 49431fb..f308074 100644
--- a/common/mock_prefs.h
+++ b/common/mock_prefs.h
@@ -29,27 +29,24 @@
class MockPrefs : public PrefsInterface {
public:
- MOCK_CONST_METHOD2(GetString,
- bool(const std::string& key, std::string* value));
- MOCK_METHOD2(SetString, bool(const std::string& key, std::string_view value));
- MOCK_CONST_METHOD2(GetInt64, bool(const std::string& key, int64_t* value));
- MOCK_METHOD2(SetInt64, bool(const std::string& key, const int64_t value));
+ MOCK_CONST_METHOD2(GetString, bool(std::string_view key, std::string* value));
+ MOCK_METHOD2(SetString, bool(std::string_view key, std::string_view value));
+ MOCK_CONST_METHOD2(GetInt64, bool(std::string_view key, int64_t* value));
+ MOCK_METHOD2(SetInt64, bool(std::string_view key, const int64_t value));
- MOCK_CONST_METHOD2(GetBoolean, bool(const std::string& key, bool* value));
- MOCK_METHOD2(SetBoolean, bool(const std::string& key, const bool value));
+ MOCK_CONST_METHOD2(GetBoolean, bool(std::string_view key, bool* value));
+ MOCK_METHOD2(SetBoolean, bool(std::string_view key, const bool value));
- MOCK_CONST_METHOD1(Exists, bool(const std::string& key));
- MOCK_METHOD1(Delete, bool(const std::string& key));
+ MOCK_CONST_METHOD1(Exists, bool(std::string_view key));
+ MOCK_METHOD1(Delete, bool(std::string_view key));
MOCK_METHOD2(Delete,
- bool(const std::string& key,
- const std::vector<std::string>& nss));
+ bool(std::string_view key, const std::vector<std::string>& nss));
MOCK_CONST_METHOD2(GetSubKeys,
- bool(const std::string&, std::vector<std::string>*));
+ bool(std::string_view, std::vector<std::string>*));
- MOCK_METHOD2(AddObserver, void(const std::string& key, ObserverInterface*));
- MOCK_METHOD2(RemoveObserver,
- void(const std::string& key, ObserverInterface*));
+ MOCK_METHOD2(AddObserver, void(std::string_view key, ObserverInterface*));
+ MOCK_METHOD2(RemoveObserver, void(std::string_view key, ObserverInterface*));
};
} // namespace chromeos_update_engine
diff --git a/common/prefs.cc b/common/prefs.cc
index 1e06be4..f33a8a9 100644
--- a/common/prefs.cc
+++ b/common/prefs.cc
@@ -51,11 +51,11 @@
} // namespace
-bool PrefsBase::GetString(const string& key, string* value) const {
+bool PrefsBase::GetString(const std::string_view key, string* value) const {
return storage_->GetKey(key, value);
}
-bool PrefsBase::SetString(const string& key, std::string_view value) {
+bool PrefsBase::SetString(std::string_view key, std::string_view value) {
TEST_AND_RETURN_FALSE(storage_->SetKey(key, value));
const auto observers_for_key = observers_.find(key);
if (observers_for_key != observers_.end()) {
@@ -66,7 +66,7 @@
return true;
}
-bool PrefsBase::GetInt64(const string& key, int64_t* value) const {
+bool PrefsBase::GetInt64(const std::string_view key, int64_t* value) const {
string str_value;
if (!GetString(key, &str_value))
return false;
@@ -75,11 +75,11 @@
return true;
}
-bool PrefsBase::SetInt64(const string& key, const int64_t value) {
+bool PrefsBase::SetInt64(std::string_view key, const int64_t value) {
return SetString(key, base::NumberToString(value));
}
-bool PrefsBase::GetBoolean(const string& key, bool* value) const {
+bool PrefsBase::GetBoolean(std::string_view key, bool* value) const {
string str_value;
if (!GetString(key, &str_value))
return false;
@@ -95,15 +95,15 @@
return false;
}
-bool PrefsBase::SetBoolean(const string& key, const bool value) {
+bool PrefsBase::SetBoolean(std::string_view key, const bool value) {
return SetString(key, value ? "true" : "false");
}
-bool PrefsBase::Exists(const string& key) const {
+bool PrefsBase::Exists(std::string_view key) const {
return storage_->KeyExists(key);
}
-bool PrefsBase::Delete(const string& key) {
+bool PrefsBase::Delete(std::string_view key) {
TEST_AND_RETURN_FALSE(storage_->DeleteKey(key));
const auto observers_for_key = observers_.find(key);
if (observers_for_key != observers_.end()) {
@@ -114,7 +114,7 @@
return true;
}
-bool PrefsBase::Delete(const string& pref_key, const vector<string>& nss) {
+bool PrefsBase::Delete(std::string_view pref_key, const vector<string>& nss) {
// Delete pref key for platform.
bool success = Delete(pref_key);
// Delete pref key in each namespace.
@@ -132,16 +132,18 @@
return success;
}
-bool PrefsBase::GetSubKeys(const string& ns, vector<string>* keys) const {
+bool PrefsBase::GetSubKeys(std::string_view ns, vector<string>* keys) const {
return storage_->GetSubKeys(ns, keys);
}
-void PrefsBase::AddObserver(const string& key, ObserverInterface* observer) {
- observers_[key].push_back(observer);
+void PrefsBase::AddObserver(std::string_view key, ObserverInterface* observer) {
+ observers_[std::string{key}].push_back(observer);
}
-void PrefsBase::RemoveObserver(const string& key, ObserverInterface* observer) {
- std::vector<ObserverInterface*>& observers_for_key = observers_[key];
+void PrefsBase::RemoveObserver(std::string_view key,
+ ObserverInterface* observer) {
+ std::vector<ObserverInterface*>& observers_for_key =
+ observers_[std::string{key}];
auto observer_it =
std::find(observers_for_key.begin(), observers_for_key.end(), observer);
if (observer_it != observers_for_key.end())
@@ -165,7 +167,7 @@
return true;
}
-bool Prefs::FileStorage::GetKey(const string& key, string* value) const {
+bool Prefs::FileStorage::GetKey(std::string_view key, string* value) const {
base::FilePath filename;
TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
if (!base::ReadFileToString(filename, value)) {
@@ -174,7 +176,7 @@
return true;
}
-bool Prefs::FileStorage::GetSubKeys(const string& ns,
+bool Prefs::FileStorage::GetSubKeys(std::string_view ns,
vector<string>* keys) const {
base::FilePath filename;
TEST_AND_RETURN_FALSE(GetFileNameForKey(ns, &filename));
@@ -192,7 +194,7 @@
return true;
}
-bool Prefs::FileStorage::SetKey(const string& key, std::string_view value) {
+bool Prefs::FileStorage::SetKey(std::string_view key, std::string_view value) {
base::FilePath filename;
TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
if (!base::DirectoryExists(filename.DirName())) {
@@ -205,13 +207,13 @@
return true;
}
-bool Prefs::FileStorage::KeyExists(const string& key) const {
+bool Prefs::FileStorage::KeyExists(std::string_view key) const {
base::FilePath filename;
TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
return base::PathExists(filename);
}
-bool Prefs::FileStorage::DeleteKey(const string& key) {
+bool Prefs::FileStorage::DeleteKey(std::string_view key) {
base::FilePath filename;
TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
#if BASE_VER < 800000
@@ -222,20 +224,21 @@
return true;
}
-bool Prefs::FileStorage::GetFileNameForKey(const string& key,
+bool Prefs::FileStorage::GetFileNameForKey(std::string_view key,
base::FilePath* filename) const {
// Allows only non-empty keys containing [A-Za-z0-9_-/].
TEST_AND_RETURN_FALSE(!key.empty());
for (char c : key)
TEST_AND_RETURN_FALSE(base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) ||
c == '_' || c == '-' || c == kKeySeparator);
- *filename = prefs_dir_.Append(key);
+ *filename = prefs_dir_.Append(
+ base::FilePath::StringPieceType(key.data(), key.size()));
return true;
}
// MemoryPrefs
-bool MemoryPrefs::MemoryStorage::GetKey(const string& key,
+bool MemoryPrefs::MemoryStorage::GetKey(std::string_view key,
string* value) const {
auto it = values_.find(key);
if (it == values_.end())
@@ -244,15 +247,13 @@
return true;
}
-bool MemoryPrefs::MemoryStorage::GetSubKeys(const string& ns,
+bool MemoryPrefs::MemoryStorage::GetSubKeys(std::string_view ns,
vector<string>* keys) const {
- using value_type = decltype(values_)::value_type;
- using key_type = decltype(values_)::key_type;
- auto lower_comp = [](const value_type& pr, const key_type& ns) {
- return pr.first.substr(0, ns.length()) < ns;
+ auto lower_comp = [](const auto& pr, const auto& ns) {
+ return std::string_view{pr.first.data(), ns.length()} < ns;
};
- auto upper_comp = [](const key_type& ns, const value_type& pr) {
- return ns < pr.first.substr(0, ns.length());
+ auto upper_comp = [](const auto& ns, const auto& pr) {
+ return ns < std::string_view{pr.first.data(), ns.length()};
};
auto lower_it =
std::lower_bound(begin(values_), end(values_), ns, lower_comp);
@@ -262,17 +263,17 @@
return true;
}
-bool MemoryPrefs::MemoryStorage::SetKey(const string& key,
+bool MemoryPrefs::MemoryStorage::SetKey(std::string_view key,
std::string_view value) {
- values_[key] = value;
+ values_[std::string{key}] = value;
return true;
}
-bool MemoryPrefs::MemoryStorage::KeyExists(const string& key) const {
+bool MemoryPrefs::MemoryStorage::KeyExists(std::string_view key) const {
return values_.find(key) != values_.end();
}
-bool MemoryPrefs::MemoryStorage::DeleteKey(const string& key) {
+bool MemoryPrefs::MemoryStorage::DeleteKey(std::string_view key) {
auto it = values_.find(key);
if (it != values_.end())
values_.erase(it);
diff --git a/common/prefs.h b/common/prefs.h
index 93477dd..c3105c6 100644
--- a/common/prefs.h
+++ b/common/prefs.h
@@ -17,6 +17,7 @@
#ifndef UPDATE_ENGINE_COMMON_PREFS_H_
#define UPDATE_ENGINE_COMMON_PREFS_H_
+#include <functional>
#include <map>
#include <string>
#include <string_view>
@@ -41,23 +42,23 @@
// Get the key named |key| and store its value in the referenced |value|.
// Returns whether the operation succeeded.
- virtual bool GetKey(const std::string& key, std::string* value) const = 0;
+ virtual bool GetKey(std::string_view key, std::string* value) const = 0;
// Get the keys stored within the namespace. If there are no keys in the
// namespace, |keys| will be empty. Returns whether the operation succeeded.
- virtual bool GetSubKeys(const std::string& ns,
+ virtual bool GetSubKeys(std::string_view ns,
std::vector<std::string>* keys) const = 0;
// Set the value of the key named |key| to |value| regardless of the
// previous value. Returns whether the operation succeeded.
- virtual bool SetKey(const std::string& key, std::string_view value) = 0;
+ virtual bool SetKey(std::string_view key, std::string_view value) = 0;
// Returns whether the key named |key| exists.
- virtual bool KeyExists(const std::string& key) const = 0;
+ virtual bool KeyExists(std::string_view key) const = 0;
// Deletes the value associated with the key name |key|. Returns whether the
// key was deleted.
- virtual bool DeleteKey(const std::string& key) = 0;
+ virtual bool DeleteKey(std::string_view key) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(StorageInterface);
@@ -66,29 +67,29 @@
explicit PrefsBase(StorageInterface* storage) : storage_(storage) {}
// PrefsInterface methods.
- bool GetString(const std::string& key, std::string* value) const override;
- bool SetString(const std::string& key, std::string_view value) override;
- bool GetInt64(const std::string& key, int64_t* value) const override;
- bool SetInt64(const std::string& key, const int64_t value) override;
- bool GetBoolean(const std::string& key, bool* value) const override;
- bool SetBoolean(const std::string& key, const bool value) override;
+ bool GetString(std::string_view key, std::string* value) const override;
+ bool SetString(std::string_view key, std::string_view value) override;
+ bool GetInt64(std::string_view key, int64_t* value) const override;
+ bool SetInt64(std::string_view key, const int64_t value) override;
+ bool GetBoolean(std::string_view key, bool* value) const override;
+ bool SetBoolean(std::string_view key, const bool value) override;
- bool Exists(const std::string& key) const override;
- bool Delete(const std::string& key) override;
- bool Delete(const std::string& pref_key,
+ bool Exists(std::string_view key) const override;
+ bool Delete(std::string_view key) override;
+ bool Delete(std::string_view pref_key,
const std::vector<std::string>& nss) override;
- bool GetSubKeys(const std::string& ns,
+ bool GetSubKeys(std::string_view ns,
std::vector<std::string>* keys) const override;
- void AddObserver(const std::string& key,
- ObserverInterface* observer) override;
- void RemoveObserver(const std::string& key,
+ void AddObserver(std::string_view key, ObserverInterface* observer) override;
+ void RemoveObserver(std::string_view key,
ObserverInterface* observer) override;
private:
// The registered observers watching for changes.
- std::map<std::string, std::vector<ObserverInterface*>> observers_;
+ std::map<std::string, std::vector<ObserverInterface*>, std::less<>>
+ observers_;
// The concrete implementation of the storage used for the keys.
StorageInterface* storage_;
@@ -121,12 +122,12 @@
bool Init(const base::FilePath& prefs_dir);
// PrefsBase::StorageInterface overrides.
- bool GetKey(const std::string& key, std::string* value) const override;
- bool GetSubKeys(const std::string& ns,
+ bool GetKey(std::string_view key, std::string* value) const override;
+ bool GetSubKeys(std::string_view ns,
std::vector<std::string>* keys) const override;
- bool SetKey(const std::string& key, std::string_view value) override;
- bool KeyExists(const std::string& key) const override;
- bool DeleteKey(const std::string& key) override;
+ bool SetKey(std::string_view key, std::string_view value) override;
+ bool KeyExists(std::string_view key) const override;
+ bool DeleteKey(std::string_view key) override;
private:
FRIEND_TEST(PrefsTest, GetFileNameForKey);
@@ -135,7 +136,7 @@
// Sets |filename| to the full path to the file containing the data
// associated with |key|. Returns true on success, false otherwise.
- bool GetFileNameForKey(const std::string& key,
+ bool GetFileNameForKey(std::string_view key,
base::FilePath* filename) const;
// Preference store directory.
@@ -161,16 +162,16 @@
MemoryStorage() = default;
// PrefsBase::StorageInterface overrides.
- bool GetKey(const std::string& key, std::string* value) const override;
- bool GetSubKeys(const std::string& ns,
+ bool GetKey(std::string_view, std::string* value) const override;
+ bool GetSubKeys(std::string_view ns,
std::vector<std::string>* keys) const override;
- bool SetKey(const std::string& key, std::string_view value) override;
- bool KeyExists(const std::string& key) const override;
- bool DeleteKey(const std::string& key) override;
+ bool SetKey(std::string_view key, std::string_view value) override;
+ bool KeyExists(std::string_view key) const override;
+ bool DeleteKey(std::string_view key) override;
private:
// The std::map holding the values in memory.
- std::map<std::string, std::string> values_;
+ std::map<std::string, std::string, std::less<>> values_;
};
// The concrete memory storage implementation.
diff --git a/common/prefs_interface.h b/common/prefs_interface.h
index e773a35..69ccf68 100644
--- a/common/prefs_interface.h
+++ b/common/prefs_interface.h
@@ -37,10 +37,10 @@
virtual ~ObserverInterface() = default;
// Called when the value is set for the observed |key|.
- virtual void OnPrefSet(const std::string& key) = 0;
+ virtual void OnPrefSet(std::string_view key) = 0;
// Called when the observed |key| is deleted.
- virtual void OnPrefDeleted(const std::string& key) = 0;
+ virtual void OnPrefDeleted(std::string_view key) = 0;
};
virtual ~PrefsInterface() = default;
@@ -48,61 +48,61 @@
// Gets a string |value| associated with |key|. Returns true on
// success, false on failure (including when the |key| is not
// present in the store).
- virtual bool GetString(const std::string& key, std::string* value) const = 0;
+ virtual bool GetString(std::string_view key, std::string* value) const = 0;
// Associates |key| with a string |value|. Returns true on success,
// false otherwise.
- virtual bool SetString(const std::string& key, std::string_view value) = 0;
+ virtual bool SetString(std::string_view key, std::string_view value) = 0;
// Gets an int64_t |value| associated with |key|. Returns true on
// success, false on failure (including when the |key| is not
// present in the store).
- virtual bool GetInt64(const std::string& key, int64_t* value) const = 0;
+ virtual bool GetInt64(std::string_view key, int64_t* value) const = 0;
// Associates |key| with an int64_t |value|. Returns true on success,
// false otherwise.
- virtual bool SetInt64(const std::string& key, const int64_t value) = 0;
+ virtual bool SetInt64(std::string_view key, const int64_t value) = 0;
// Gets a boolean |value| associated with |key|. Returns true on
// success, false on failure (including when the |key| is not
// present in the store).
- virtual bool GetBoolean(const std::string& key, bool* value) const = 0;
+ virtual bool GetBoolean(std::string_view key, bool* value) const = 0;
// Associates |key| with a boolean |value|. Returns true on success,
// false otherwise.
- virtual bool SetBoolean(const std::string& key, const bool value) = 0;
+ virtual bool SetBoolean(std::string_view key, const bool value) = 0;
// Returns true if the setting exists (i.e. a file with the given key
// exists in the prefs directory)
- virtual bool Exists(const std::string& key) const = 0;
+ virtual bool Exists(std::string_view key) const = 0;
// Returns true if successfully deleted the file corresponding to
// this key. Calling with non-existent keys does nothing.
- virtual bool Delete(const std::string& key) = 0;
+ virtual bool Delete(std::string_view key) = 0;
// Deletes the pref key from platform and given namespace subdirectories.
// Keys are matched against end of pref keys in each namespace.
// Returns true if all deletes were successful.
- virtual bool Delete(const std::string& pref_key,
+ virtual bool Delete(std::string_view pref_key,
const std::vector<std::string>& nss) = 0;
// Creates a key which is part of a sub preference.
static std::string CreateSubKey(const std::vector<std::string>& ns_with_key);
// Returns a list of keys within the namespace.
- virtual bool GetSubKeys(const std::string& ns,
+ virtual bool GetSubKeys(std::string_view ns,
std::vector<std::string>* keys) const = 0;
// Add an observer to watch whenever the given |key| is modified. The
// OnPrefSet() and OnPrefDelete() methods will be called whenever any of the
// Set*() methods or the Delete() method are called on the given key,
// respectively.
- virtual void AddObserver(const std::string& key,
+ virtual void AddObserver(std::string_view key,
ObserverInterface* observer) = 0;
// Remove an observer added with AddObserver(). The observer won't be called
// anymore for future Set*() and Delete() method calls.
- virtual void RemoveObserver(const std::string& key,
+ virtual void RemoveObserver(std::string_view key,
ObserverInterface* observer) = 0;
protected:
diff --git a/common/prefs_unittest.cc b/common/prefs_unittest.cc
index a5f46e5..cef6d44 100644
--- a/common/prefs_unittest.cc
+++ b/common/prefs_unittest.cc
@@ -507,8 +507,8 @@
class MockPrefsObserver : public PrefsInterface::ObserverInterface {
public:
- MOCK_METHOD1(OnPrefSet, void(const string&));
- MOCK_METHOD1(OnPrefDeleted, void(const string& key));
+ MOCK_METHOD1(OnPrefSet, void(std::string_view));
+ MOCK_METHOD1(OnPrefDeleted, void(std::string_view));
};
TEST_F(PrefsTest, ObserversCalled) {
diff --git a/common/scoped_task_id.h b/common/scoped_task_id.h
new file mode 100644
index 0000000..91a2986
--- /dev/null
+++ b/common/scoped_task_id.h
@@ -0,0 +1,123 @@
+//
+// Copyright (C) 2021 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_SCOPED_TASK_ID_H_
+#define UPDATE_ENGINE_SCOPED_TASK_ID_H_
+
+#include <type_traits>
+#include <utility>
+
+#include <base/bind.h>
+#include <brillo/message_loops/message_loop.h>
+
+namespace chromeos_update_engine {
+
+// This class provides unique_ptr like semantic for |MessageLoop::TaskId|, when
+// instance of this class goes out of scope, underlying task will be cancelled.
+class ScopedTaskId {
+ using MessageLoop = brillo::MessageLoop;
+
+ public:
+ // Move only type similar to unique_ptr.
+ ScopedTaskId(const ScopedTaskId&) = delete;
+ ScopedTaskId& operator=(const ScopedTaskId&) = delete;
+
+ constexpr ScopedTaskId() = default;
+
+ constexpr ScopedTaskId(ScopedTaskId&& other) noexcept {
+ *this = std::move(other);
+ }
+
+ constexpr ScopedTaskId& operator=(ScopedTaskId&& other) noexcept {
+ std::swap(task_id_, other.task_id_);
+ return *this;
+ }
+
+ // Post a callback on current message loop, return true if succeeded, false if
+ // the previous callback hasn't run yet, or scheduling failed at MessageLoop
+ // side.
+ [[nodiscard]] bool PostTask(const base::Location& from_here,
+ base::OnceClosure&& callback,
+ base::TimeDelta delay = {}) noexcept {
+ return PostTask<decltype(callback)>(from_here, std::move(callback), delay);
+ }
+ [[nodiscard]] bool PostTask(const base::Location& from_here,
+ std::function<void()>&& callback,
+ base::TimeDelta delay = {}) noexcept {
+ return PostTask<decltype(callback)>(from_here, std::move(callback), delay);
+ }
+
+ ~ScopedTaskId() noexcept { Cancel(); }
+
+ // Cancel the underlying managed task, true if cancel successful. False if no
+ // task scheduled or task cancellation failed
+ bool Cancel() noexcept {
+ if (task_id_ != MessageLoop::kTaskIdNull) {
+ if (MessageLoop::current()->CancelTask(task_id_)) {
+ LOG(INFO) << "Cancelled task id " << task_id_;
+ task_id_ = MessageLoop::kTaskIdNull;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ [[nodiscard]] constexpr bool IsScheduled() const noexcept {
+ return task_id_ != MessageLoop::kTaskIdNull;
+ }
+
+ [[nodiscard]] constexpr bool operator==(const ScopedTaskId& other) const
+ noexcept {
+ return other.task_id_ == task_id_;
+ }
+
+ [[nodiscard]] constexpr bool operator<(const ScopedTaskId& other) const
+ noexcept {
+ return task_id_ < other.task_id_;
+ }
+
+ private:
+ template <typename Callable>
+ [[nodiscard]] bool PostTask(const base::Location& from_here,
+ Callable&& callback,
+ base::TimeDelta delay) noexcept {
+ if (task_id_ != MessageLoop::kTaskIdNull) {
+ LOG(ERROR) << "Scheduling another task but task id " << task_id_
+ << " isn't executed yet! This can cause the old task to leak.";
+ return false;
+ }
+ task_id_ = MessageLoop::current()->PostDelayedTask(
+ from_here,
+ base::BindOnce(&ScopedTaskId::ExecuteTask<decltype(callback)>,
+ base::Unretained(this),
+ std::move(callback)),
+ delay);
+ return task_id_ != MessageLoop::kTaskIdNull;
+ }
+ template <typename Callable>
+ void ExecuteTask(Callable&& callback) {
+ task_id_ = MessageLoop::kTaskIdNull;
+ if constexpr (std::is_same_v<Callable&&, base::OnceClosure&&>) {
+ std::move(callback).Run();
+ } else {
+ std::move(callback)();
+ }
+ }
+ MessageLoop::TaskId task_id_{MessageLoop::kTaskIdNull};
+};
+} // namespace chromeos_update_engine
+
+#endif
diff --git a/common/utils.h b/common/utils.h
index 5f6e475..59f236e 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -399,13 +399,19 @@
// If |open_fd| is true, a writable file descriptor will be opened for this
// file.
- explicit ScopedTempFile(const std::string& pattern, bool open_fd = false) {
+ // If |truncate_size| is non-zero, truncate file to that size on creation.
+ explicit ScopedTempFile(const std::string& pattern,
+ bool open_fd = false,
+ size_t truncate_size = 0) {
CHECK(utils::MakeTempFile(pattern, &path_, open_fd ? &fd_ : nullptr));
unlinker_.reset(new ScopedPathUnlinker(path_));
if (open_fd) {
CHECK_GE(fd_, 0);
fd_closer_.reset(new ScopedFdCloser(&fd_));
}
+ if (truncate_size > 0) {
+ CHECK_EQ(0, truncate(path_.c_str(), truncate_size));
+ }
}
virtual ~ScopedTempFile() = default;
diff --git a/metrics_utils.cc b/metrics_utils.cc
index 34da5a1..ade024a 100644
--- a/metrics_utils.cc
+++ b/metrics_utils.cc
@@ -294,7 +294,7 @@
return metrics::ConnectionType::kUnknown;
}
-int64_t GetPersistedValue(const std::string& key, PrefsInterface* prefs) {
+int64_t GetPersistedValue(std::string_view key, PrefsInterface* prefs) {
CHECK(prefs);
if (!prefs->Exists(key))
return 0;
diff --git a/metrics_utils.h b/metrics_utils.h
index 3aac4e5..16e9eec 100644
--- a/metrics_utils.h
+++ b/metrics_utils.h
@@ -50,7 +50,7 @@
// Returns the persisted value from prefs for the given key. It also
// validates that the value returned is non-negative.
-int64_t GetPersistedValue(const std::string& key, PrefsInterface* prefs);
+int64_t GetPersistedValue(std::string_view key, PrefsInterface* prefs);
// Persists the reboot count of the update attempt to |kPrefsNumReboots|.
void SetNumReboots(int64_t num_reboots, PrefsInterface* prefs);
diff --git a/payload_consumer/bzip_extent_writer.cc b/payload_consumer/bzip_extent_writer.cc
index 0c25c71..26fdc5f 100644
--- a/payload_consumer/bzip_extent_writer.cc
+++ b/payload_consumer/bzip_extent_writer.cc
@@ -29,8 +29,7 @@
TEST_AND_RETURN(input_buffer_.empty());
}
-bool BzipExtentWriter::Init(FileDescriptorPtr fd,
- const RepeatedPtrField<Extent>& extents,
+bool BzipExtentWriter::Init(const RepeatedPtrField<Extent>& extents,
uint32_t block_size) {
// Init bzip2 stream
int rc = BZ2_bzDecompressInit(&stream_,
@@ -39,7 +38,7 @@
TEST_AND_RETURN_FALSE(rc == BZ_OK);
- return next_->Init(fd, extents, block_size);
+ return next_->Init(extents, block_size);
}
bool BzipExtentWriter::Write(const void* bytes, size_t count) {
diff --git a/payload_consumer/bzip_extent_writer.h b/payload_consumer/bzip_extent_writer.h
index ec181a7..38c041a 100644
--- a/payload_consumer/bzip_extent_writer.h
+++ b/payload_consumer/bzip_extent_writer.h
@@ -40,8 +40,7 @@
}
~BzipExtentWriter() override;
- bool Init(FileDescriptorPtr fd,
- const google::protobuf::RepeatedPtrField<Extent>& extents,
+ bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents,
uint32_t block_size) override;
bool Write(const void* bytes, size_t count) override;
diff --git a/payload_consumer/bzip_extent_writer_unittest.cc b/payload_consumer/bzip_extent_writer_unittest.cc
index b587040..c93545a 100644
--- a/payload_consumer/bzip_extent_writer_unittest.cc
+++ b/payload_consumer/bzip_extent_writer_unittest.cc
@@ -29,7 +29,6 @@
#include "update_engine/common/utils.h"
#include "update_engine/payload_generator/extent_ranges.h"
-using google::protobuf::RepeatedPtrField;
using std::min;
using std::string;
using std::vector;
@@ -64,9 +63,8 @@
0x22, 0x9c, 0x28, 0x48, 0x66, 0x61, 0xb8, 0xea, 0x00,
};
- BzipExtentWriter bzip_writer(std::make_unique<DirectExtentWriter>());
- EXPECT_TRUE(
- bzip_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize));
+ BzipExtentWriter bzip_writer(std::make_unique<DirectExtentWriter>(fd_));
+ EXPECT_TRUE(bzip_writer.Init({extents.begin(), extents.end()}, kBlockSize));
EXPECT_TRUE(bzip_writer.Write(test, sizeof(test)));
brillo::Blob buf;
@@ -97,9 +95,8 @@
vector<Extent> extents = {ExtentForBytes(kBlockSize, 0, kDecompressedLength)};
- BzipExtentWriter bzip_writer(std::make_unique<DirectExtentWriter>());
- EXPECT_TRUE(
- bzip_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize));
+ BzipExtentWriter bzip_writer(std::make_unique<DirectExtentWriter>(fd_));
+ EXPECT_TRUE(bzip_writer.Init({extents.begin(), extents.end()}, kBlockSize));
brillo::Blob original_compressed_data = compressed_data;
for (brillo::Blob::size_type i = 0; i < compressed_data.size();
diff --git a/payload_consumer/cow_writer_file_descriptor.cc b/payload_consumer/cow_writer_file_descriptor.cc
index d8c7afb..2de6664 100644
--- a/payload_consumer/cow_writer_file_descriptor.cc
+++ b/payload_consumer/cow_writer_file_descriptor.cc
@@ -28,7 +28,10 @@
CowWriterFileDescriptor::CowWriterFileDescriptor(
std::unique_ptr<android::snapshot::ISnapshotWriter> cow_writer)
: cow_writer_(std::move(cow_writer)),
- cow_reader_(cow_writer_->OpenReader()) {}
+ cow_reader_(cow_writer_->OpenReader()) {
+ CHECK_NE(cow_writer_, nullptr);
+ CHECK_NE(cow_reader_, nullptr);
+}
bool CowWriterFileDescriptor::Open(const char* path, int flags, mode_t mode) {
LOG(ERROR) << "CowWriterFileDescriptor doesn't support Open()";
@@ -113,7 +116,17 @@
bool CowWriterFileDescriptor::Close() {
if (cow_writer_) {
- TEST_AND_RETURN_FALSE(cow_writer_->Finalize());
+ // b/186196758
+ // When calling
+ // InitializeAppend(kEndOfInstall), the SnapshotWriter only reads up to the
+ // given label. But OpenReader() completely disregards the resume label and
+ // reads all ops. Therefore, update_engine sees the verity data. However,
+ // when calling SnapshotWriter::Finalize(), data after resume label are
+ // discarded, therefore verity data is gone. To prevent phantom reads, don't
+ // call Finalize() unless we actually write something.
+ if (dirty_) {
+ TEST_AND_RETURN_FALSE(cow_writer_->Finalize());
+ }
cow_writer_ = nullptr;
}
if (cow_reader_) {
diff --git a/payload_consumer/cow_writer_file_descriptor_unittest.cc b/payload_consumer/cow_writer_file_descriptor_unittest.cc
new file mode 100644
index 0000000..c596e3b
--- /dev/null
+++ b/payload_consumer/cow_writer_file_descriptor_unittest.cc
@@ -0,0 +1,120 @@
+//
+// Copyright (C) 2021 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/payload_consumer/cow_writer_file_descriptor.h"
+
+#include <cstring>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <libsnapshot/snapshot_writer.h>
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+constexpr size_t BLOCK_SIZE = 4096;
+constexpr size_t PARTITION_SIZE = BLOCK_SIZE * 10;
+
+using android::base::unique_fd;
+using android::snapshot::CompressedSnapshotWriter;
+using android::snapshot::CowOptions;
+using android::snapshot::ISnapshotWriter;
+
+class CowWriterFileDescriptorUnittest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ ASSERT_EQ(ftruncate64(cow_device_file_.fd(), PARTITION_SIZE), 0)
+ << "Failed to truncate cow_device file to " << PARTITION_SIZE
+ << strerror(errno);
+ ASSERT_EQ(ftruncate64(cow_source_file_.fd(), PARTITION_SIZE), 0)
+ << "Failed to truncate cow_source file to " << PARTITION_SIZE
+ << strerror(errno);
+ }
+
+ std::unique_ptr<CompressedSnapshotWriter> GetCowWriter() {
+ const CowOptions options{.block_size = BLOCK_SIZE, .compression = "gz"};
+ auto snapshot_writer = std::make_unique<CompressedSnapshotWriter>(options);
+ int fd = open(cow_device_file_.path().c_str(), O_RDWR);
+ EXPECT_NE(fd, -1);
+ EXPECT_TRUE(snapshot_writer->SetCowDevice(unique_fd{fd}));
+ snapshot_writer->SetSourceDevice(cow_source_file_.path());
+ return snapshot_writer;
+ }
+ CowWriterFileDescriptor GetCowFd() {
+ auto cow_writer = GetCowWriter();
+ return CowWriterFileDescriptor{std::move(cow_writer)};
+ }
+
+ ScopedTempFile cow_source_file_{"cow_source.XXXXXX", true};
+ ScopedTempFile cow_device_file_{"cow_device.XXXXXX", true};
+};
+
+TEST_F(CowWriterFileDescriptorUnittest, ReadAfterWrite) {
+ std::vector<unsigned char> buffer;
+ buffer.resize(BLOCK_SIZE);
+ std::fill(buffer.begin(), buffer.end(), 234);
+
+ std::vector<unsigned char> verity_data;
+ verity_data.resize(BLOCK_SIZE);
+ std::fill(verity_data.begin(), verity_data.end(), 0xAA);
+
+ auto cow_writer = GetCowWriter();
+ cow_writer->Initialize();
+
+ // Simulate Writing InstallOp data
+ ASSERT_TRUE(cow_writer->AddRawBlocks(0, buffer.data(), buffer.size()));
+ ASSERT_TRUE(cow_writer->AddZeroBlocks(1, 2));
+ ASSERT_TRUE(cow_writer->AddCopy(3, 1));
+ // Fake label to simulate "end of install"
+ ASSERT_TRUE(cow_writer->AddLabel(23));
+ ASSERT_TRUE(
+ cow_writer->AddRawBlocks(4, verity_data.data(), verity_data.size()));
+ ASSERT_TRUE(cow_writer->Finalize());
+
+ cow_writer = GetCowWriter();
+ ASSERT_NE(nullptr, cow_writer);
+ ASSERT_TRUE(cow_writer->InitializeAppend(23));
+ auto cow_fd =
+ std::make_unique<CowWriterFileDescriptor>(std::move(cow_writer));
+
+ ASSERT_EQ((ssize_t)BLOCK_SIZE * 4, cow_fd->Seek(BLOCK_SIZE * 4, SEEK_SET));
+ std::vector<unsigned char> read_back(4096);
+ ASSERT_EQ((ssize_t)read_back.size(),
+ cow_fd->Read(read_back.data(), read_back.size()));
+ ASSERT_EQ(verity_data, read_back);
+
+ // Since we didn't write anything to this instance of cow_fd, destructor
+ // should not call Finalize(). As finalize will drop ops after resume label,
+ // causing subsequent reads to fail.
+ cow_writer = GetCowWriter();
+ ASSERT_NE(nullptr, cow_writer);
+ ASSERT_TRUE(cow_writer->InitializeAppend(23));
+ cow_fd = std::make_unique<CowWriterFileDescriptor>(std::move(cow_writer));
+
+ ASSERT_EQ((ssize_t)BLOCK_SIZE * 4, cow_fd->Seek(BLOCK_SIZE * 4, SEEK_SET));
+ ASSERT_EQ((ssize_t)read_back.size(),
+ cow_fd->Read(read_back.data(), read_back.size()));
+ ASSERT_EQ(verity_data, read_back)
+ << "Could not read verity data afeter InitializeAppend() => Read() => "
+ "InitializeAppend() sequence. If no writes happened while CowWriterFd "
+ "is open, Finalize() should not be called.";
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc
index 840ecf6..27f846e 100644
--- a/payload_consumer/delta_performer_unittest.cc
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -654,7 +654,9 @@
brillo::Blob payload_data =
GeneratePayload(brillo::Blob(), {aop}, false, &old_part);
- EXPECT_EQ(actual_data, ApplyPayload(payload_data, source.path(), false));
+ // When source hash mismatches, PartitionWriter will refuse to write anything.
+ // Therefore we should expect an empty blob.
+ EXPECT_EQ(brillo::Blob{}, ApplyPayload(payload_data, source.path(), false));
}
TEST_F(DeltaPerformerTest, ExtentsToByteStringTest) {
diff --git a/payload_consumer/extent_writer.h b/payload_consumer/extent_writer.h
index 9e53561..8b1b532 100644
--- a/payload_consumer/extent_writer.h
+++ b/payload_consumer/extent_writer.h
@@ -38,8 +38,7 @@
virtual ~ExtentWriter() = default;
// Returns true on success.
- virtual bool Init(FileDescriptorPtr fd,
- const google::protobuf::RepeatedPtrField<Extent>& extents,
+ virtual bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents,
uint32_t block_size) = 0;
// Returns true on success.
@@ -51,13 +50,11 @@
class DirectExtentWriter : public ExtentWriter {
public:
- DirectExtentWriter() = default;
+ explicit DirectExtentWriter(FileDescriptorPtr fd) : fd_(fd) {}
~DirectExtentWriter() override = default;
- bool Init(FileDescriptorPtr fd,
- const google::protobuf::RepeatedPtrField<Extent>& extents,
+ bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents,
uint32_t block_size) override {
- fd_ = fd;
block_size_ = block_size;
extents_ = extents;
cur_extent_ = extents_.begin();
diff --git a/payload_consumer/extent_writer_unittest.cc b/payload_consumer/extent_writer_unittest.cc
index afebb1a..5c67d3e 100644
--- a/payload_consumer/extent_writer_unittest.cc
+++ b/payload_consumer/extent_writer_unittest.cc
@@ -65,9 +65,8 @@
TEST_F(ExtentWriterTest, SimpleTest) {
vector<Extent> extents = {ExtentForRange(1, 1)};
const string bytes = "1234";
- DirectExtentWriter direct_writer;
- EXPECT_TRUE(
- direct_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize));
+ DirectExtentWriter direct_writer{fd_};
+ EXPECT_TRUE(direct_writer.Init({extents.begin(), extents.end()}, kBlockSize));
EXPECT_TRUE(direct_writer.Write(bytes.data(), bytes.size()));
EXPECT_EQ(static_cast<off_t>(kBlockSize + bytes.size()),
@@ -84,9 +83,8 @@
TEST_F(ExtentWriterTest, ZeroLengthTest) {
vector<Extent> extents = {ExtentForRange(1, 1)};
- DirectExtentWriter direct_writer;
- EXPECT_TRUE(
- direct_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize));
+ DirectExtentWriter direct_writer{fd_};
+ EXPECT_TRUE(direct_writer.Init({extents.begin(), extents.end()}, kBlockSize));
EXPECT_TRUE(direct_writer.Write(nullptr, 0));
}
@@ -109,9 +107,8 @@
brillo::Blob data(kBlockSize * 3);
test_utils::FillWithData(&data);
- DirectExtentWriter direct_writer;
- EXPECT_TRUE(
- direct_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize));
+ DirectExtentWriter direct_writer{fd_};
+ EXPECT_TRUE(direct_writer.Init({extents.begin(), extents.end()}, kBlockSize));
size_t bytes_written = 0;
while (bytes_written < data.size()) {
@@ -150,9 +147,8 @@
brillo::Blob data(17);
test_utils::FillWithData(&data);
- DirectExtentWriter direct_writer;
- EXPECT_TRUE(
- direct_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize));
+ DirectExtentWriter direct_writer{fd_};
+ EXPECT_TRUE(direct_writer.Init({extents.begin(), extents.end()}, kBlockSize));
size_t bytes_written = 0;
while (bytes_written < (block_count * kBlockSize)) {
diff --git a/payload_consumer/fake_extent_writer.h b/payload_consumer/fake_extent_writer.h
index 7b2b7ac..680b1b3 100644
--- a/payload_consumer/fake_extent_writer.h
+++ b/payload_consumer/fake_extent_writer.h
@@ -33,8 +33,7 @@
~FakeExtentWriter() override = default;
// ExtentWriter overrides.
- bool Init(FileDescriptorPtr /* fd */,
- const google::protobuf::RepeatedPtrField<Extent>& /* extents */,
+ bool Init(const google::protobuf::RepeatedPtrField<Extent>& /* extents */,
uint32_t /* block_size */) override {
init_called_ = true;
return true;
diff --git a/payload_consumer/file_descriptor.cc b/payload_consumer/file_descriptor.cc
index 7c69c1b..da76327 100644
--- a/payload_consumer/file_descriptor.cc
+++ b/payload_consumer/file_descriptor.cc
@@ -139,7 +139,9 @@
}
bool EintrSafeFileDescriptor::Close() {
- CHECK_GE(fd_, 0);
+ if (fd_ < 0) {
+ return false;
+ }
// https://stackoverflow.com/questions/705454/does-linux-guarantee-the-contents-of-a-file-is-flushed-to-disc-after-close
// |close()| doesn't imply |fsync()|, we need to do it manually.
fsync(fd_);
diff --git a/payload_consumer/file_descriptor_utils.cc b/payload_consumer/file_descriptor_utils.cc
index 846cbd7..9a6a601 100644
--- a/payload_consumer/file_descriptor_utils.cc
+++ b/payload_consumer/file_descriptor_utils.cc
@@ -82,8 +82,8 @@
const RepeatedPtrField<Extent>& tgt_extents,
uint64_t block_size,
brillo::Blob* hash_out) {
- DirectExtentWriter writer;
- TEST_AND_RETURN_FALSE(writer.Init(target, tgt_extents, block_size));
+ DirectExtentWriter writer{target};
+ TEST_AND_RETURN_FALSE(writer.Init(tgt_extents, block_size));
TEST_AND_RETURN_FALSE(utils::BlocksInExtents(src_extents) ==
utils::BlocksInExtents(tgt_extents));
TEST_AND_RETURN_FALSE(
diff --git a/payload_consumer/filesystem_verifier_action.cc b/payload_consumer/filesystem_verifier_action.cc
index 09dc638..22c8e0b 100644
--- a/payload_consumer/filesystem_verifier_action.cc
+++ b/payload_consumer/filesystem_verifier_action.cc
@@ -35,6 +35,7 @@
#include <brillo/secure_blob.h>
#include <brillo/streams/file_stream.h>
+#include "common/error_code.h"
#include "payload_generator/delta_diff_generator.h"
#include "update_engine/common/utils.h"
#include "update_engine/payload_consumer/file_descriptor.h"
@@ -77,6 +78,7 @@
namespace {
const off_t kReadFileBufferSize = 128 * 1024;
+constexpr float kVerityProgressPercent = 0.6;
} // namespace
void FilesystemVerifierAction::PerformAction() {
@@ -102,7 +104,6 @@
}
void FilesystemVerifierAction::TerminateProcessing() {
- brillo::MessageLoop::current()->CancelTask(pending_task_id_);
cancelled_ = true;
Cleanup(ErrorCode::kSuccess); // error code is ignored if canceled_ is true.
}
@@ -112,6 +113,14 @@
// This memory is not used anymore.
buffer_.clear();
+ // If we didn't write verity, partitions were maped. Releaase resource now.
+ if (!install_plan_.write_verity &&
+ dynamic_control_->UpdateUsesSnapshotCompression()) {
+ LOG(INFO) << "Not writing verity and VABC is enabled, unmapping all "
+ "partitions";
+ dynamic_control_->UnmapAllPartitions();
+ }
+
if (cancelled_)
return;
if (code == ErrorCode::kSuccess && HasOutputPipe())
@@ -126,11 +135,42 @@
}
}
-bool FilesystemVerifierAction::InitializeFdVABC() {
+void FilesystemVerifierAction::UpdatePartitionProgress(double progress) {
+ // We don't consider sizes of each partition. Every partition
+ // has the same length on progress bar.
+ // TODO(b/186087589): Take sizes of each partition into account.
+ UpdateProgress((progress + partition_index_) /
+ install_plan_.partitions.size());
+}
+
+bool FilesystemVerifierAction::InitializeFdVABC(bool should_write_verity) {
const InstallPlan::Partition& partition =
install_plan_.partitions[partition_index_];
- // FilesystemVerifierAction need the read_fd_.
+ if (!should_write_verity) {
+ // In VABC, we cannot map/unmap partitions w/o first closing ALL fds first.
+ // Since this function might be called inside a ScheduledTask, the closure
+ // might have a copy of partition_fd_ when executing this function. Which
+ // means even if we do |partition_fd_.reset()| here, there's a chance that
+ // underlying fd isn't closed until we return. This is unacceptable, we need
+ // to close |partition_fd| right away.
+ if (partition_fd_) {
+ partition_fd_->Close();
+ partition_fd_.reset();
+ }
+ // In VABC, if we are not writing verity, just map all partitions,
+ // and read using regular fd on |postinstall_mount_device| .
+ // All read will go through snapuserd, which provides a consistent
+ // view: device will use snapuserd to read partition during boot.
+ // b/186196758
+ // Call UnmapAllPartitions() first, because if we wrote verity before, these
+ // writes won't be visible to previously opened snapuserd daemon. To ensure
+ // that we will see the most up to date data from partitions, call Unmap()
+ // then Map() to re-spin daemon.
+ dynamic_control_->UnmapAllPartitions();
+ dynamic_control_->MapAllPartitions();
+ return InitializeFd(partition.readonly_target_path);
+ }
partition_fd_ =
dynamic_control_->OpenCowFd(partition.name, partition.source_path, true);
if (!partition_fd_) {
@@ -157,6 +197,112 @@
return true;
}
+void FilesystemVerifierAction::WriteVerityAndHashPartition(
+ FileDescriptorPtr fd,
+ const off64_t start_offset,
+ const off64_t end_offset,
+ void* buffer,
+ const size_t buffer_size) {
+ if (start_offset >= end_offset) {
+ LOG_IF(WARNING, start_offset > end_offset)
+ << "start_offset is greater than end_offset : " << start_offset << " > "
+ << end_offset;
+ if (!verity_writer_->Finalize(fd, fd)) {
+ LOG(ERROR) << "Failed to write verity data";
+ Cleanup(ErrorCode::kVerityCalculationError);
+ return;
+ }
+ if (dynamic_control_->UpdateUsesSnapshotCompression()) {
+ // Spin up snapuserd to read fs.
+ if (!InitializeFdVABC(false)) {
+ LOG(ERROR) << "Failed to map all partitions";
+ Cleanup(ErrorCode::kFilesystemVerifierError);
+ return;
+ }
+ }
+ HashPartition(partition_fd_, 0, partition_size_, buffer, buffer_size);
+ return;
+ }
+ const auto cur_offset = fd->Seek(start_offset, SEEK_SET);
+ if (cur_offset != start_offset) {
+ PLOG(ERROR) << "Failed to seek to offset: " << start_offset;
+ Cleanup(ErrorCode::kVerityCalculationError);
+ return;
+ }
+ const auto read_size =
+ std::min<size_t>(buffer_size, end_offset - start_offset);
+ const auto bytes_read = fd->Read(buffer, read_size);
+ if (bytes_read < 0 || static_cast<size_t>(bytes_read) != read_size) {
+ PLOG(ERROR) << "Failed to read offset " << start_offset << " expected "
+ << read_size << " bytes, actual: " << bytes_read;
+ Cleanup(ErrorCode::kVerityCalculationError);
+ return;
+ }
+ if (!verity_writer_->Update(
+ start_offset, static_cast<const uint8_t*>(buffer), read_size)) {
+ LOG(ERROR) << "VerityWriter::Update() failed";
+ Cleanup(ErrorCode::kVerityCalculationError);
+ return;
+ }
+ UpdatePartitionProgress((start_offset + bytes_read) * 1.0f / partition_size_ *
+ kVerityProgressPercent);
+ CHECK(pending_task_id_.PostTask(
+ FROM_HERE,
+ base::BindOnce(&FilesystemVerifierAction::WriteVerityAndHashPartition,
+ base::Unretained(this),
+ fd,
+ start_offset + bytes_read,
+ end_offset,
+ buffer,
+ buffer_size)));
+}
+
+void FilesystemVerifierAction::HashPartition(FileDescriptorPtr fd,
+ const off64_t start_offset,
+ const off64_t end_offset,
+ void* buffer,
+ const size_t buffer_size) {
+ if (start_offset >= end_offset) {
+ LOG_IF(WARNING, start_offset > end_offset)
+ << "start_offset is greater than end_offset : " << start_offset << " > "
+ << end_offset;
+ FinishPartitionHashing();
+ return;
+ }
+ const auto cur_offset = fd->Seek(start_offset, SEEK_SET);
+ if (cur_offset != start_offset) {
+ PLOG(ERROR) << "Failed to seek to offset: " << start_offset;
+ Cleanup(ErrorCode::kFilesystemVerifierError);
+ return;
+ }
+ const auto read_size =
+ std::min<size_t>(buffer_size, end_offset - start_offset);
+ const auto bytes_read = fd->Read(buffer, read_size);
+ if (bytes_read < 0 || static_cast<size_t>(bytes_read) != read_size) {
+ PLOG(ERROR) << "Failed to read offset " << start_offset << " expected "
+ << read_size << " bytes, actual: " << bytes_read;
+ Cleanup(ErrorCode::kFilesystemVerifierError);
+ return;
+ }
+ if (!hasher_->Update(buffer, read_size)) {
+ LOG(ERROR) << "Hasher updated failed on offset" << start_offset;
+ Cleanup(ErrorCode::kFilesystemVerifierError);
+ return;
+ }
+ const auto progress = (start_offset + bytes_read) * 1.0f / partition_size_;
+ UpdatePartitionProgress(progress * (1 - kVerityProgressPercent) +
+ kVerityProgressPercent);
+ CHECK(pending_task_id_.PostTask(
+ FROM_HERE,
+ base::BindOnce(&FilesystemVerifierAction::HashPartition,
+ base::Unretained(this),
+ fd,
+ start_offset + bytes_read,
+ end_offset,
+ buffer,
+ buffer_size)));
+}
+
void FilesystemVerifierAction::StartPartitionHashing() {
if (partition_index_ == install_plan_.partitions.size()) {
if (!install_plan_.untouched_dynamic_partitions.empty()) {
@@ -178,26 +324,14 @@
}
const InstallPlan::Partition& partition =
install_plan_.partitions[partition_index_];
- string part_path;
- switch (verifier_step_) {
- case VerifierStep::kVerifySourceHash:
- part_path = partition.source_path;
- partition_size_ = partition.source_size;
- break;
- case VerifierStep::kVerifyTargetHash:
- part_path = partition.target_path;
- partition_size_ = partition.target_size;
- break;
- }
+ const auto& part_path = GetPartitionPath();
+ partition_size_ = GetPartitionSize();
LOG(INFO) << "Hashing partition " << partition_index_ << " ("
<< partition.name << ") on device " << part_path;
auto success = false;
- if (dynamic_control_->UpdateUsesSnapshotCompression() &&
- verifier_step_ == VerifierStep::kVerifyTargetHash &&
- dynamic_control_->IsDynamicPartition(partition.name,
- install_plan_.target_slot)) {
- success = InitializeFdVABC();
+ if (IsVABC(partition)) {
+ success = InitializeFdVABC(ShouldWriteVerity());
} else {
if (part_path.empty()) {
if (partition_size_ == 0) {
@@ -232,17 +366,53 @@
filesystem_data_end_ = partition.fec_offset;
}
if (ShouldWriteVerity()) {
+ LOG(INFO) << "Verity writes enabled on partition " << partition.name;
if (!verity_writer_->Init(partition)) {
LOG(INFO) << "Verity writes enabled on partition " << partition.name;
Cleanup(ErrorCode::kVerityCalculationError);
return;
}
+ WriteVerityAndHashPartition(
+ partition_fd_, 0, filesystem_data_end_, buffer_.data(), buffer_.size());
} else {
LOG(INFO) << "Verity writes disabled on partition " << partition.name;
+ HashPartition(
+ partition_fd_, 0, partition_size_, buffer_.data(), buffer_.size());
}
+}
- // Start the first read.
- ScheduleFileSystemRead();
+bool FilesystemVerifierAction::IsVABC(
+ const InstallPlan::Partition& partition) const {
+ return dynamic_control_->UpdateUsesSnapshotCompression() &&
+ verifier_step_ == VerifierStep::kVerifyTargetHash &&
+ dynamic_control_->IsDynamicPartition(partition.name,
+ install_plan_.target_slot);
+}
+
+const std::string& FilesystemVerifierAction::GetPartitionPath() const {
+ const InstallPlan::Partition& partition =
+ install_plan_.partitions[partition_index_];
+ switch (verifier_step_) {
+ case VerifierStep::kVerifySourceHash:
+ return partition.source_path;
+ case VerifierStep::kVerifyTargetHash:
+ if (IsVABC(partition)) {
+ return partition.readonly_target_path;
+ } else {
+ return partition.target_path;
+ }
+ }
+}
+
+size_t FilesystemVerifierAction::GetPartitionSize() const {
+ const InstallPlan::Partition& partition =
+ install_plan_.partitions[partition_index_];
+ switch (verifier_step_) {
+ case VerifierStep::kVerifySourceHash:
+ return partition.source_size;
+ case VerifierStep::kVerifyTargetHash:
+ return partition.target_size;
+ }
}
bool FilesystemVerifierAction::ShouldWriteVerity() {
@@ -253,106 +423,6 @@
(partition.hash_tree_size > 0 || partition.fec_size > 0);
}
-void FilesystemVerifierAction::ReadVerityAndFooter() {
- if (ShouldWriteVerity()) {
- if (!verity_writer_->Finalize(partition_fd_, partition_fd_)) {
- LOG(ERROR) << "Failed to write hashtree/FEC data.";
- Cleanup(ErrorCode::kFilesystemVerifierError);
- return;
- }
- }
- // Since we handed our |read_fd_| to verity_writer_ during |Finalize()|
- // call, fd's position could have been changed. Re-seek.
- partition_fd_->Seek(filesystem_data_end_, SEEK_SET);
- auto bytes_to_read = partition_size_ - filesystem_data_end_;
- while (bytes_to_read > 0) {
- const auto read_size = std::min<size_t>(buffer_.size(), bytes_to_read);
- auto bytes_read = partition_fd_->Read(buffer_.data(), read_size);
- if (bytes_read <= 0) {
- PLOG(ERROR) << "Failed to read hash tree " << bytes_read;
- Cleanup(ErrorCode::kFilesystemVerifierError);
- return;
- }
- if (!hasher_->Update(buffer_.data(), bytes_read)) {
- LOG(ERROR) << "Unable to update the hash.";
- Cleanup(ErrorCode::kError);
- return;
- }
- bytes_to_read -= bytes_read;
- }
- FinishPartitionHashing();
-}
-
-void FilesystemVerifierAction::ScheduleFileSystemRead() {
- // We can only start reading anything past |hash_tree_offset| after we have
- // already read all the data blocks that the hash tree covers. The same
- // applies to FEC.
-
- size_t bytes_to_read = std::min(static_cast<uint64_t>(buffer_.size()),
- filesystem_data_end_ - offset_);
- if (!bytes_to_read) {
- ReadVerityAndFooter();
- return;
- }
- partition_fd_->Seek(offset_, SEEK_SET);
- auto bytes_read = partition_fd_->Read(buffer_.data(), bytes_to_read);
- if (bytes_read < 0) {
- LOG(ERROR) << "Unable to schedule an asynchronous read from the stream. "
- << bytes_read;
- Cleanup(ErrorCode::kError);
- } else {
- // We could just invoke |OnReadDoneCallback()|, it works. But |PostTask|
- // is used so that users can cancel updates.
- pending_task_id_ = brillo::MessageLoop::current()->PostTask(
- base::Bind(&FilesystemVerifierAction::OnReadDone,
- base::Unretained(this),
- bytes_read));
- }
-}
-
-void FilesystemVerifierAction::OnReadDone(size_t bytes_read) {
- if (cancelled_) {
- Cleanup(ErrorCode::kError);
- return;
- }
- if (bytes_read == 0) {
- LOG(ERROR) << "Failed to read the remaining " << partition_size_ - offset_
- << " bytes from partition "
- << install_plan_.partitions[partition_index_].name;
- Cleanup(ErrorCode::kFilesystemVerifierError);
- return;
- }
-
- if (!hasher_->Update(buffer_.data(), bytes_read)) {
- LOG(ERROR) << "Unable to update the hash.";
- Cleanup(ErrorCode::kError);
- return;
- }
-
- // WE don't consider sizes of each partition. Every partition
- // has the same length on progress bar.
- // TODO(zhangkelvin) Take sizes of each partition into account
-
- UpdateProgress(
- (static_cast<double>(offset_) / partition_size_ + partition_index_) /
- install_plan_.partitions.size());
- if (ShouldWriteVerity()) {
- if (!verity_writer_->Update(offset_, buffer_.data(), bytes_read)) {
- LOG(ERROR) << "Unable to update verity";
- Cleanup(ErrorCode::kVerityCalculationError);
- return;
- }
- }
-
- offset_ += bytes_read;
- if (offset_ == filesystem_data_end_) {
- ReadVerityAndFooter();
- return;
- }
-
- ScheduleFileSystemRead();
-}
-
void FilesystemVerifierAction::FinishPartitionHashing() {
if (!hasher_->Finalize()) {
LOG(ERROR) << "Unable to finalize the hash.";
@@ -424,6 +494,7 @@
hasher_.reset();
buffer_.clear();
if (partition_fd_) {
+ partition_fd_->Close();
partition_fd_.reset();
}
StartPartitionHashing();
diff --git a/payload_consumer/filesystem_verifier_action.h b/payload_consumer/filesystem_verifier_action.h
index 78634cb..850abda 100644
--- a/payload_consumer/filesystem_verifier_action.h
+++ b/payload_consumer/filesystem_verifier_action.h
@@ -22,12 +22,14 @@
#include <memory>
#include <string>
+#include <utility>
#include <vector>
#include <brillo/message_loops/message_loop.h>
#include "update_engine/common/action.h"
#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/scoped_task_id.h"
#include "update_engine/payload_consumer/file_descriptor.h"
#include "update_engine/payload_consumer/install_plan.h"
#include "update_engine/payload_consumer/verity_writer_interface.h"
@@ -84,6 +86,16 @@
private:
friend class FilesystemVerifierActionTestDelegate;
+ void WriteVerityAndHashPartition(FileDescriptorPtr fd,
+ const off64_t start_offset,
+ const off64_t end_offset,
+ void* buffer,
+ const size_t buffer_size);
+ void HashPartition(FileDescriptorPtr fd,
+ const off64_t start_offset,
+ const off64_t end_offset,
+ void* buffer,
+ const size_t buffer_size);
// Return true if we need to write verity bytes.
bool ShouldWriteVerity();
@@ -91,16 +103,11 @@
// remaining to be hashed, it finishes the action.
void StartPartitionHashing();
- // Schedules the asynchronous read of the filesystem part of this
- // partition(not including hashtree/verity).
- void ScheduleFileSystemRead();
+ const std::string& GetPartitionPath() const;
- // Read the verity part of this partition.(hash tree and FEC)
- void ReadVerityAndFooter();
+ bool IsVABC(const InstallPlan::Partition& partition) const;
- // Called from the main loop when a single read from |src_stream_| succeeds or
- // fails, calling OnReadDoneCallback() and OnReadErrorCallback() respectively.
- void OnReadDone(size_t bytes_read);
+ size_t GetPartitionSize() const;
// When the read is done, finalize the hash checking of the current partition
// and continue checking the next one.
@@ -114,9 +121,13 @@
// Invoke delegate callback to report progress, if delegate is not null
void UpdateProgress(double progress);
+ // Updates progress of current partition. |progress| should be in range [0,
+ // 1], and it will be scaled appropriately with # of partitions.
+ void UpdatePartitionProgress(double progress);
+
// Initialize read_fd_ and write_fd_
bool InitializeFd(const std::string& part_path);
- bool InitializeFdVABC();
+ bool InitializeFdVABC(bool should_write_verity);
// The type of the partition that we are verifying.
VerifierStep verifier_step_ = VerifierStep::kVerifyTargetHash;
@@ -161,8 +172,7 @@
// Callback that should be cancelled on |TerminateProcessing|. Usually this
// points to pending read callbacks from async stream.
- brillo::MessageLoop::TaskId pending_task_id_{
- brillo::MessageLoop::kTaskIdNull};
+ ScopedTaskId pending_task_id_;
DISALLOW_COPY_AND_ASSIGN(FilesystemVerifierAction);
};
diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc
index c100684..f2f2954 100644
--- a/payload_consumer/filesystem_verifier_action_unittest.cc
+++ b/payload_consumer/filesystem_verifier_action_unittest.cc
@@ -16,6 +16,8 @@
#include "update_engine/payload_consumer/filesystem_verifier_action.h"
+#include <algorithm>
+#include <cstring>
#include <memory>
#include <string>
#include <utility>
@@ -25,8 +27,10 @@
#include <brillo/message_loops/fake_message_loop.h>
#include <brillo/message_loops/message_loop_utils.h>
#include <brillo/secure_blob.h>
+#include <fec/ecc.h>
#include <gtest/gtest.h>
#include <libsnapshot/snapshot_writer.h>
+#include <sys/stat.h>
#include "update_engine/common/dynamic_partition_control_stub.h"
#include "update_engine/common/hash_calculator.h"
@@ -35,6 +39,7 @@
#include "update_engine/common/utils.h"
#include "update_engine/payload_consumer/fake_file_descriptor.h"
#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/payload_consumer/verity_writer_android.h"
using brillo::MessageLoop;
using std::string;
@@ -48,13 +53,57 @@
namespace chromeos_update_engine {
class FilesystemVerifierActionTest : public ::testing::Test {
+ public:
+ static constexpr size_t BLOCK_SIZE = 4096;
+ // We use SHA256 for testing, so hash size is 256bits / 8
+ static constexpr size_t HASH_SIZE = 256 / 8;
+ static constexpr size_t PARTITION_SIZE = BLOCK_SIZE * 1024;
+ static constexpr size_t HASH_TREE_START_OFFSET = 800 * BLOCK_SIZE;
+ size_t hash_tree_size = 0;
+ size_t fec_start_offset = 0;
+ size_t fec_data_size = 0;
+ static constexpr size_t FEC_ROOTS = 2;
+ size_t fec_rounds = 0;
+ size_t fec_size = 0;
+
protected:
- void SetUp() override { loop_.SetAsCurrent(); }
+ void SetUp() override {
+ hash_tree_size = HashTreeBuilder::CalculateSize(
+ HASH_TREE_START_OFFSET, BLOCK_SIZE, HASH_SIZE);
+ fec_start_offset = HASH_TREE_START_OFFSET + hash_tree_size;
+ fec_data_size = fec_start_offset;
+ static constexpr size_t FEC_ROOTS = 2;
+ fec_rounds =
+ utils::DivRoundUp(fec_data_size / BLOCK_SIZE, FEC_RSM - FEC_ROOTS);
+ fec_size = fec_rounds * FEC_ROOTS * BLOCK_SIZE;
+
+ fec_data_.resize(fec_size);
+ hash_tree_data_.resize(hash_tree_size);
+ // Globally readable writable, as we want to write data
+ ASSERT_EQ(0, fchmod(source_part_.fd(), 0666))
+ << " Failed to set " << source_part_.path() << " as writable "
+ << strerror(errno);
+ ASSERT_EQ(0, fchmod(target_part_.fd(), 0666))
+ << " Failed to set " << target_part_.path() << " as writable "
+ << strerror(errno);
+ brillo::Blob part_data(PARTITION_SIZE);
+ test_utils::FillWithData(&part_data);
+ ASSERT_TRUE(utils::WriteFile(
+ source_part_.path().c_str(), part_data.data(), part_data.size()));
+ // FillWithData() will fill with different data next call. We want
+ // source/target partitions to contain different data for testing.
+ test_utils::FillWithData(&part_data);
+ ASSERT_TRUE(utils::WriteFile(
+ target_part_.path().c_str(), part_data.data(), part_data.size()));
+ loop_.SetAsCurrent();
+ }
void TearDown() override {
EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1));
}
+ void DoTestVABC(bool clear_target_hash, bool enable_verity);
+
// Returns true iff test has completed successfully.
bool DoTest(bool terminate_early, bool hash_fail);
@@ -62,11 +111,110 @@
void BuildActions(const InstallPlan& install_plan,
DynamicPartitionControlInterface* dynamic_control);
+ InstallPlan::Partition* AddFakePartition(InstallPlan* install_plan,
+ std::string name = "fake_part") {
+ InstallPlan::Partition& part = install_plan->partitions.emplace_back();
+ part.name = name;
+ part.target_path = target_part_.path();
+ part.readonly_target_path = part.target_path;
+ part.target_size = PARTITION_SIZE;
+ part.block_size = BLOCK_SIZE;
+ part.source_path = source_part_.path();
+ part.source_size = PARTITION_SIZE;
+ EXPECT_TRUE(
+ HashCalculator::RawHashOfFile(source_part_.path(), &part.source_hash));
+ EXPECT_TRUE(
+ HashCalculator::RawHashOfFile(target_part_.path(), &part.target_hash));
+ return ∂
+ }
+ static void ZeroRange(FileDescriptorPtr fd,
+ size_t start_block,
+ size_t num_blocks) {
+ std::vector<unsigned char> buffer(BLOCK_SIZE);
+ ASSERT_EQ((ssize_t)(start_block * BLOCK_SIZE),
+ fd->Seek(start_block * BLOCK_SIZE, SEEK_SET));
+ for (size_t i = 0; i < num_blocks; i++) {
+ ASSERT_TRUE(utils::WriteAll(fd, buffer.data(), buffer.size()));
+ }
+ }
+
+ void SetHashWithVerity(InstallPlan::Partition* partition) {
+ partition->hash_tree_algorithm = "sha256";
+ partition->hash_tree_size = hash_tree_size;
+ partition->hash_tree_offset = HASH_TREE_START_OFFSET;
+ partition->hash_tree_data_offset = 0;
+ partition->hash_tree_data_size = HASH_TREE_START_OFFSET;
+ partition->fec_size = fec_size;
+ partition->fec_offset = fec_start_offset;
+ partition->fec_data_offset = 0;
+ partition->fec_data_size = fec_data_size;
+ partition->fec_roots = FEC_ROOTS;
+ VerityWriterAndroid verity_writer;
+ ASSERT_TRUE(verity_writer.Init(*partition));
+ LOG(INFO) << "Opening " << partition->readonly_target_path;
+ auto fd = std::make_shared<EintrSafeFileDescriptor>();
+ ASSERT_TRUE(fd->Open(partition->readonly_target_path.c_str(), O_RDWR))
+ << "Failed to open " << partition->target_path.c_str() << " "
+ << strerror(errno);
+ std::vector<unsigned char> buffer(BLOCK_SIZE);
+ // Only need to read up to hash tree
+ auto bytes_to_read = HASH_TREE_START_OFFSET;
+ auto offset = 0;
+ while (bytes_to_read > 0) {
+ const auto bytes_read = fd->Read(
+ buffer.data(), std::min<size_t>(buffer.size(), bytes_to_read));
+ ASSERT_GT(bytes_read, 0)
+ << "offset: " << offset << " bytes to read: " << bytes_to_read
+ << " error: " << strerror(errno);
+ ASSERT_TRUE(verity_writer.Update(offset, buffer.data(), bytes_read));
+ bytes_to_read -= bytes_read;
+ offset += bytes_read;
+ }
+ ASSERT_TRUE(verity_writer.Finalize(fd, fd));
+ ASSERT_TRUE(fd->IsOpen());
+ ASSERT_TRUE(HashCalculator::RawHashOfFile(target_part_.path(),
+ &partition->target_hash));
+
+ ASSERT_TRUE(fd->Seek(HASH_TREE_START_OFFSET, SEEK_SET));
+ ASSERT_EQ(fd->Read(hash_tree_data_.data(), hash_tree_data_.size()),
+ static_cast<ssize_t>(hash_tree_data_.size()))
+ << "Failed to read hashtree " << strerror(errno);
+ ASSERT_TRUE(fd->Seek(fec_start_offset, SEEK_SET));
+ ASSERT_EQ(fd->Read(fec_data_.data(), fec_data_.size()),
+ static_cast<ssize_t>(fec_data_.size()))
+ << "Failed to read FEC " << strerror(errno);
+ // Fs verification action is expected to write them, so clear verity data to
+ // ensure that they are re-created correctly.
+ ZeroRange(
+ fd, HASH_TREE_START_OFFSET / BLOCK_SIZE, hash_tree_size / BLOCK_SIZE);
+ ZeroRange(fd, fec_start_offset / BLOCK_SIZE, fec_size / BLOCK_SIZE);
+ }
+
brillo::FakeMessageLoop loop_{nullptr};
ActionProcessor processor_;
DynamicPartitionControlStub dynamic_control_stub_;
+ std::vector<unsigned char> fec_data_;
+ std::vector<unsigned char> hash_tree_data_;
+ static ScopedTempFile source_part_;
+ static ScopedTempFile target_part_;
+ InstallPlan install_plan_;
};
+ScopedTempFile FilesystemVerifierActionTest::source_part_{
+ "source_part.XXXXXX", true, PARTITION_SIZE};
+ScopedTempFile FilesystemVerifierActionTest::target_part_{
+ "target_part.XXXXXX", true, PARTITION_SIZE};
+
+static void EnableVABC(MockDynamicPartitionControl* dynamic_control,
+ const std::string& part_name) {
+ ON_CALL(*dynamic_control, GetDynamicPartitionsFeatureFlag())
+ .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH)));
+ ON_CALL(*dynamic_control, UpdateUsesSnapshotCompression())
+ .WillByDefault(Return(true));
+ ON_CALL(*dynamic_control, IsDynamicPartition(part_name, _))
+ .WillByDefault(Return(true));
+}
+
class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate {
public:
FilesystemVerifierActionTestDelegate()
@@ -132,9 +280,8 @@
bool success = true;
// Set up the action objects
- InstallPlan install_plan;
- install_plan.source_slot = 0;
- install_plan.target_slot = 1;
+ install_plan_.source_slot = 0;
+ install_plan_.target_slot = 1;
InstallPlan::Partition part;
part.name = "part";
part.target_size = kLoopFileSize - (hash_fail ? 1 : 0);
@@ -149,23 +296,19 @@
ADD_FAILURE();
success = false;
}
- install_plan.partitions = {part};
+ install_plan_.partitions = {part};
- BuildActions(install_plan);
+ BuildActions(install_plan_);
FilesystemVerifierActionTestDelegate delegate;
processor_.set_delegate(&delegate);
- loop_.PostTask(FROM_HERE,
- base::Bind(
- [](ActionProcessor* processor, bool terminate_early) {
- processor->StartProcessing();
- if (terminate_early) {
- processor->StopProcessing();
- }
- },
- base::Unretained(&processor_),
- terminate_early));
+ loop_.PostTask(base::Bind(&ActionProcessor::StartProcessing,
+ base::Unretained(&processor_)));
+ if (terminate_early) {
+ loop_.PostTask(base::Bind(&ActionProcessor::StopProcessing,
+ base::Unretained(&processor_)));
+ }
loop_.Run();
if (!terminate_early) {
@@ -194,7 +337,7 @@
EXPECT_TRUE(is_a_file_reading_eq);
success = success && is_a_file_reading_eq;
- bool is_install_plan_eq = (*delegate.install_plan_ == install_plan);
+ bool is_install_plan_eq = (*delegate.install_plan_ == install_plan_);
EXPECT_TRUE(is_install_plan_eq);
success = success && is_install_plan_eq;
return success;
@@ -259,14 +402,13 @@
}
TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) {
- InstallPlan install_plan;
InstallPlan::Partition part;
part.name = "nope";
part.source_path = "/no/such/file";
part.target_path = "/no/such/file";
- install_plan.partitions = {part};
+ install_plan_.partitions = {part};
- BuildActions(install_plan);
+ BuildActions(install_plan_);
FilesystemVerifierActionTest2Delegate delegate;
processor_.set_delegate(&delegate);
@@ -307,7 +449,6 @@
test_utils::ScopedLoopbackDeviceBinder target_device(
part_file.path(), true, &target_path);
- InstallPlan install_plan;
InstallPlan::Partition part;
part.name = "part";
part.target_path = target_path;
@@ -338,9 +479,9 @@
part.hash_tree_salt = {0x9e, 0xcb, 0xf8, 0xd5, 0x0b, 0xb4, 0x43,
0x0a, 0x7a, 0x10, 0xad, 0x96, 0xd7, 0x15,
0x70, 0xba, 0xed, 0x27, 0xe2, 0xae};
- install_plan.partitions = {part};
+ install_plan_.partitions = {part};
- BuildActions(install_plan);
+ BuildActions(install_plan_);
FilesystemVerifierActionTestDelegate delegate;
processor_.set_delegate(&delegate);
@@ -369,8 +510,7 @@
test_utils::ScopedLoopbackDeviceBinder target_device(
part_file.path(), true, &target_path);
- InstallPlan install_plan;
- install_plan.write_verity = false;
+ install_plan_.write_verity = false;
InstallPlan::Partition part;
part.name = "part";
part.target_path = target_path;
@@ -385,9 +525,9 @@
part.fec_offset = part.fec_data_size;
part.fec_size = 2 * 4096;
EXPECT_TRUE(HashCalculator::RawHashOfData(part_data, &part.target_hash));
- install_plan.partitions = {part};
+ install_plan_.partitions = {part};
- BuildActions(install_plan);
+ BuildActions(install_plan_);
FilesystemVerifierActionTestDelegate delegate;
processor_.set_delegate(&delegate);
@@ -404,85 +544,146 @@
ASSERT_EQ(ErrorCode::kSuccess, delegate.code());
}
-TEST_F(FilesystemVerifierActionTest, RunWithVABCNoVerity) {
- InstallPlan install_plan;
- InstallPlan::Partition& part = install_plan.partitions.emplace_back();
- part.name = "fake_part";
- part.target_path = "/dev/fake_target_path";
- part.target_size = 4096 * 4096;
- part.block_size = 4096;
- part.source_path = "/dev/fake_source_path";
- part.fec_size = 0;
- part.hash_tree_size = 0;
- part.target_hash.clear();
- part.source_hash.clear();
+void FilesystemVerifierActionTest::DoTestVABC(bool clear_target_hash,
+ bool enable_verity) {
+ auto part_ptr = AddFakePartition(&install_plan_);
+ if (::testing::Test::HasFailure()) {
+ return;
+ }
+ ASSERT_NE(part_ptr, nullptr);
+ InstallPlan::Partition& part = *part_ptr;
+ part.target_path = "Shouldn't attempt to open this path";
+ if (enable_verity) {
+ install_plan_.write_verity = true;
+ ASSERT_NO_FATAL_FAILURE(SetHashWithVerity(&part));
+ }
+ if (clear_target_hash) {
+ part.target_hash.clear();
+ }
NiceMock<MockDynamicPartitionControl> dynamic_control;
- auto fake_fd = std::make_shared<FakeFileDescriptor>();
- ON_CALL(dynamic_control, GetDynamicPartitionsFeatureFlag())
- .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH)));
- ON_CALL(dynamic_control, UpdateUsesSnapshotCompression())
- .WillByDefault(Return(true));
- ON_CALL(dynamic_control, OpenCowFd(_, _, _)).WillByDefault(Return(fake_fd));
- ON_CALL(dynamic_control, IsDynamicPartition(part.name, _))
- .WillByDefault(Return(true));
+ EnableVABC(&dynamic_control, part.name);
+ auto open_cow = [part]() {
+ auto cow_fd = std::make_shared<EintrSafeFileDescriptor>();
+ EXPECT_TRUE(cow_fd->Open(part.readonly_target_path.c_str(), O_RDWR))
+ << "Failed to open part " << part.readonly_target_path
+ << strerror(errno);
+ return cow_fd;
+ };
EXPECT_CALL(dynamic_control, UpdateUsesSnapshotCompression())
.Times(AtLeast(1));
- EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _))
- .Times(1);
+ auto cow_fd = open_cow();
+ if (HasFailure()) {
+ return;
+ }
+
+ if (enable_verity) {
+ ON_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _))
+ .WillByDefault(open_cow);
+ EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _))
+ .Times(AtLeast(1));
+
+ // fs verification isn't supposed to write to |readonly_target_path|. All
+ // writes should go through fd returned by |OpenCowFd|. Therefore we set
+ // target part as read-only to make sure.
+ ASSERT_EQ(0, chmod(part.readonly_target_path.c_str(), 0444))
+ << " Failed to set " << part.readonly_target_path << " as read-only "
+ << strerror(errno);
+ } else {
+ // Since we are not writing verity, we should not attempt to OpenCowFd()
+ // reads should go through regular file descriptors on mapped partitions.
+ EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _))
+ .Times(0);
+ EXPECT_CALL(dynamic_control, MapAllPartitions()).Times(AtLeast(1));
+ }
EXPECT_CALL(dynamic_control, ListDynamicPartitionsForSlot(_, _, _))
.WillRepeatedly(
DoAll(SetArgPointee<2, std::vector<std::string>>({part.name}),
Return(true)));
- BuildActions(install_plan, &dynamic_control);
+ BuildActions(install_plan_, &dynamic_control);
FilesystemVerifierActionTestDelegate delegate;
processor_.set_delegate(&delegate);
- loop_.PostTask(
- FROM_HERE,
- base::Bind(
- [](ActionProcessor* processor) { processor->StartProcessing(); },
- base::Unretained(&processor_)));
+ loop_.PostTask(FROM_HERE,
+ base::Bind(&ActionProcessor::StartProcessing,
+ base::Unretained(&processor_)));
loop_.Run();
ASSERT_FALSE(processor_.IsRunning());
ASSERT_TRUE(delegate.ran());
- // Filesystem verifier will fail, because we set an empty hash
- ASSERT_EQ(ErrorCode::kNewRootfsVerificationError, delegate.code());
- const auto& read_pos = fake_fd->GetReadOps();
- size_t expected_offset = 0;
- for (const auto& [off, size] : read_pos) {
- ASSERT_EQ(off, expected_offset);
- expected_offset += size;
+ if (enable_verity) {
+ std::vector<unsigned char> actual_fec(fec_size);
+ ssize_t bytes_read = 0;
+ ASSERT_TRUE(utils::PReadAll(cow_fd,
+ actual_fec.data(),
+ actual_fec.size(),
+ fec_start_offset,
+ &bytes_read));
+ ASSERT_EQ(actual_fec, fec_data_);
+ std::vector<unsigned char> actual_hash_tree(hash_tree_size);
+ ASSERT_TRUE(utils::PReadAll(cow_fd,
+ actual_hash_tree.data(),
+ actual_hash_tree.size(),
+ HASH_TREE_START_OFFSET,
+ &bytes_read));
+ ASSERT_EQ(actual_hash_tree, hash_tree_data_);
}
- const auto actual_read_size = expected_offset;
- ASSERT_EQ(actual_read_size, part.target_size);
+ if (clear_target_hash) {
+ ASSERT_EQ(ErrorCode::kNewRootfsVerificationError, delegate.code());
+ } else {
+ ASSERT_EQ(ErrorCode::kSuccess, delegate.code());
+ }
}
-TEST_F(FilesystemVerifierActionTest, ReadAfterWrite) {
- constexpr auto BLOCK_SIZE = 4096;
- ScopedTempFile cow_device_file("cow_device.XXXXXX", true);
- android::snapshot::CompressedSnapshotWriter snapshot_writer{
- {.block_size = BLOCK_SIZE}};
- snapshot_writer.SetCowDevice(android::base::unique_fd{cow_device_file.fd()});
- snapshot_writer.Initialize();
- std::vector<unsigned char> buffer;
- buffer.resize(BLOCK_SIZE);
- std::fill(buffer.begin(), buffer.end(), 123);
+TEST_F(FilesystemVerifierActionTest, VABC_NoVerity_Success) {
+ DoTestVABC(false, false);
+}
- ASSERT_TRUE(snapshot_writer.AddRawBlocks(0, buffer.data(), buffer.size()));
- ASSERT_TRUE(snapshot_writer.Finalize());
- auto cow_reader = snapshot_writer.OpenReader();
- ASSERT_NE(cow_reader, nullptr);
- ASSERT_TRUE(snapshot_writer.AddRawBlocks(1, buffer.data(), buffer.size()));
- ASSERT_TRUE(snapshot_writer.AddRawBlocks(2, buffer.data(), buffer.size()));
- ASSERT_TRUE(snapshot_writer.Finalize());
- cow_reader = snapshot_writer.OpenReader();
- ASSERT_NE(cow_reader, nullptr);
+TEST_F(FilesystemVerifierActionTest, VABC_NoVerity_Target_Mismatch) {
+ DoTestVABC(true, false);
+}
+
+TEST_F(FilesystemVerifierActionTest, VABC_Verity_Success) {
+ DoTestVABC(false, true);
+}
+
+TEST_F(FilesystemVerifierActionTest, VABC_Verity_ReadAfterWrite) {
+ ASSERT_NO_FATAL_FAILURE(DoTestVABC(false, true));
+ // Run FS verification again, w/o writing verity. We have seen a bug where
+ // attempting to run fs again will cause previously written verity data to be
+ // dropped, so cover this scenario.
+ ASSERT_GE(install_plan_.partitions.size(), 1UL);
+ auto& part = install_plan_.partitions[0];
+ install_plan_.write_verity = false;
+ part.readonly_target_path = target_part_.path();
+ NiceMock<MockDynamicPartitionControl> dynamic_control;
+ EnableVABC(&dynamic_control, part.name);
+
+ // b/186196758 is only visible if we repeatedely run FS verification w/o
+ // writing verity
+ for (int i = 0; i < 3; i++) {
+ BuildActions(install_plan_, &dynamic_control);
+
+ FilesystemVerifierActionTestDelegate delegate;
+ processor_.set_delegate(&delegate);
+ loop_.PostTask(
+ FROM_HERE,
+ base::Bind(
+ [](ActionProcessor* processor) { processor->StartProcessing(); },
+ base::Unretained(&processor_)));
+ loop_.Run();
+ ASSERT_FALSE(processor_.IsRunning());
+ ASSERT_TRUE(delegate.ran());
+ ASSERT_EQ(ErrorCode::kSuccess, delegate.code());
+ }
+}
+
+TEST_F(FilesystemVerifierActionTest, VABC_Verity_Target_Mismatch) {
+ DoTestVABC(true, true);
}
} // namespace chromeos_update_engine
diff --git a/payload_consumer/install_plan.cc b/payload_consumer/install_plan.cc
index 39827a4..06b7dd8 100644
--- a/payload_consumer/install_plan.cc
+++ b/payload_consumer/install_plan.cc
@@ -122,6 +122,7 @@
partition.target_hash.size())},
{"run_postinstall", utils::ToString(partition.run_postinstall)},
{"postinstall_path", partition.postinstall_path},
+ {"readonly_target_path", partition.readonly_target_path},
{"filesystem_type", partition.filesystem_type},
},
"\n "));
@@ -165,7 +166,7 @@
partition.name, target_slot, source_slot);
TEST_AND_RETURN_FALSE(device.has_value());
partition.target_path = device->rw_device_path;
- partition.postinstall_mount_device = device->mountable_device_path;
+ partition.readonly_target_path = device->readonly_device_path;
} else {
partition.target_path.clear();
}
diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h
index 43b94fc..7c77789 100644
--- a/payload_consumer/install_plan.h
+++ b/payload_consumer/install_plan.h
@@ -109,7 +109,7 @@
std::string target_path;
// |mountable_target_device| is intended to be a path to block device which
// can be used for mounting this block device's underlying filesystem.
- std::string postinstall_mount_device;
+ std::string readonly_target_path;
uint64_t target_size{0};
brillo::Blob target_hash;
diff --git a/payload_consumer/install_plan_unittest.cc b/payload_consumer/install_plan_unittest.cc
index 2ca8d81..7779494 100644
--- a/payload_consumer/install_plan_unittest.cc
+++ b/payload_consumer/install_plan_unittest.cc
@@ -38,6 +38,7 @@
.source_path = "foo-source-path",
.source_hash = {0xb1, 0xb2},
.target_path = "foo-target-path",
+ .readonly_target_path = "mountable-device",
.target_hash = {0xb3, 0xb4},
.postinstall_path = "foo-path",
.filesystem_type = "foo-type",
@@ -66,6 +67,7 @@
target_hash: B3B4
run_postinstall: false
postinstall_path: foo-path
+ readonly_target_path: mountable-device
filesystem_type: foo-type
Payload: 0
urls: (url1,url2)
diff --git a/payload_consumer/partition_writer.cc b/payload_consumer/partition_writer.cc
index 6f06dd2..f2022a1 100644
--- a/payload_consumer/partition_writer.cc
+++ b/payload_consumer/partition_writer.cc
@@ -42,6 +42,7 @@
#include "update_engine/payload_consumer/mount_history.h"
#include "update_engine/payload_consumer/payload_constants.h"
#include "update_engine/payload_consumer/xz_extent_writer.h"
+#include "update_engine/payload_generator/extent_utils.h"
namespace chromeos_update_engine {
@@ -83,6 +84,8 @@
} // namespace
+using google::protobuf::RepeatedPtrField;
+
// Opens path for read/write. On success returns an open FileDescriptor
// and sets *err to 0. On failure, sets *err to errno and returns nullptr.
FileDescriptorPtr OpenFile(const char* path,
@@ -326,8 +329,7 @@
writer.reset(new XzExtentWriter(std::move(writer)));
}
- TEST_AND_RETURN_FALSE(
- writer->Init(target_fd_, operation.dst_extents(), block_size_));
+ TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
TEST_AND_RETURN_FALSE(writer->Write(data, operation.data_length()));
return true;
@@ -366,6 +368,23 @@
return true;
}
+std::ostream& operator<<(std::ostream& out,
+ const RepeatedPtrField<Extent>& extents) {
+ if (extents.size() == 0) {
+ out << "[]";
+ return out;
+ }
+ out << "[";
+ auto begin = extents.begin();
+ out << *begin;
+ for (int i = 1; i < extents.size(); i++) {
+ ++begin;
+ out << ", " << *begin;
+ }
+ out << "]";
+ return out;
+}
+
bool PartitionWriter::PerformSourceCopyOperation(
const InstallOperation& operation, ErrorCode* error) {
TEST_AND_RETURN_FALSE(source_fd_ != nullptr);
@@ -374,107 +393,29 @@
// Being this a device-specific optimization let DynamicPartitionController
// decide it the operation should be skipped.
const PartitionUpdate& partition = partition_update_;
- const auto& partition_control = dynamic_control_;
InstallOperation buf;
- bool should_optimize = partition_control->OptimizeOperation(
+ const bool should_optimize = dynamic_control_->OptimizeOperation(
partition.partition_name(), operation, &buf);
const InstallOperation& optimized = should_optimize ? buf : operation;
- if (operation.has_src_sha256_hash()) {
- bool read_ok;
- brillo::Blob source_hash;
- brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(),
- operation.src_sha256_hash().end());
-
- // We fall back to use the error corrected device if the hash of the raw
- // device doesn't match or there was an error reading the source partition.
- // Note that this code will also fall back if writing the target partition
- // fails.
- if (should_optimize) {
- // Hash operation.src_extents(), then copy optimized.src_extents to
- // optimized.dst_extents.
- read_ok =
- fd_utils::ReadAndHashExtents(
- source_fd_, operation.src_extents(), block_size_, &source_hash) &&
- fd_utils::CopyAndHashExtents(source_fd_,
- optimized.src_extents(),
- target_fd_,
- optimized.dst_extents(),
- block_size_,
- nullptr /* skip hashing */);
- } else {
- read_ok = fd_utils::CopyAndHashExtents(source_fd_,
- operation.src_extents(),
- target_fd_,
- operation.dst_extents(),
- block_size_,
- &source_hash);
- }
- if (read_ok && expected_source_hash == source_hash)
- return true;
- LOG(WARNING) << "Source hash from RAW device mismatched, attempting to "
- "correct using ECC";
- if (!OpenCurrentECCPartition()) {
- // The following function call will return false since the source hash
- // mismatches, but we still want to call it so it prints the appropriate
- // log message.
- return ValidateSourceHash(source_hash, operation, source_fd_, error);
- }
-
- LOG(WARNING) << "Source hash from RAW device mismatched: found "
- << base::HexEncode(source_hash.data(), source_hash.size())
- << ", expected "
- << base::HexEncode(expected_source_hash.data(),
- expected_source_hash.size());
- if (should_optimize) {
- TEST_AND_RETURN_FALSE(fd_utils::ReadAndHashExtents(
- source_ecc_fd_, operation.src_extents(), block_size_, &source_hash));
- TEST_AND_RETURN_FALSE(
- fd_utils::CopyAndHashExtents(source_ecc_fd_,
- optimized.src_extents(),
- target_fd_,
- optimized.dst_extents(),
- block_size_,
- nullptr /* skip hashing */));
- } else {
- TEST_AND_RETURN_FALSE(
- fd_utils::CopyAndHashExtents(source_ecc_fd_,
- operation.src_extents(),
- target_fd_,
- operation.dst_extents(),
- block_size_,
- &source_hash));
- }
- TEST_AND_RETURN_FALSE(
- ValidateSourceHash(source_hash, operation, source_ecc_fd_, error));
- // At this point reading from the error corrected device worked, but
- // reading from the raw device failed, so this is considered a recovered
- // failure.
- source_ecc_recovered_failures_++;
- } else {
- // When the operation doesn't include a source hash, we attempt the error
- // corrected device first since we can't verify the block in the raw device
- // at this point, but we fall back to the raw device since the error
- // corrected device can be shorter or not available.
-
- if (OpenCurrentECCPartition() &&
- fd_utils::CopyAndHashExtents(source_ecc_fd_,
- optimized.src_extents(),
- target_fd_,
- optimized.dst_extents(),
- block_size_,
- nullptr)) {
- return true;
- }
- TEST_AND_RETURN_FALSE(fd_utils::CopyAndHashExtents(source_fd_,
- optimized.src_extents(),
- target_fd_,
- optimized.dst_extents(),
- block_size_,
- nullptr));
+ // Invoke ChooseSourceFD with original operation, so that it can properly
+ // verify source hashes. Optimized operation might contain a smaller set of
+ // extents, or completely empty.
+ auto source_fd = ChooseSourceFD(operation, error);
+ if (source_fd == nullptr) {
+ LOG(ERROR) << "Unrecoverable source hash mismatch found on partition "
+ << partition.partition_name()
+ << " extents: " << operation.src_extents();
+ return false;
}
- return true;
+
+ return fd_utils::CopyAndHashExtents(source_fd,
+ optimized.src_extents(),
+ target_fd_,
+ optimized.dst_extents(),
+ block_size_,
+ nullptr);
}
bool PartitionWriter::PerformSourceBsdiffOperation(
@@ -493,8 +434,7 @@
utils::BlocksInExtents(operation.src_extents()) * block_size_);
auto writer = CreateBaseExtentWriter();
- TEST_AND_RETURN_FALSE(
- writer->Init(target_fd_, operation.dst_extents(), block_size_));
+ TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
auto dst_file = std::make_unique<BsdiffExtentFile>(
std::move(writer),
utils::BlocksInExtents(operation.dst_extents()) * block_size_);
@@ -522,8 +462,7 @@
utils::BlocksInExtents(operation.src_extents()) * block_size_));
auto writer = CreateBaseExtentWriter();
- TEST_AND_RETURN_FALSE(
- writer->Init(target_fd_, operation.dst_extents(), block_size_));
+ TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
puffin::UniqueStreamPtr dst_stream(new PuffinExtentStream(
std::move(writer),
utils::BlocksInExtents(operation.dst_extents()) * block_size_));
@@ -658,7 +597,7 @@
}
std::unique_ptr<ExtentWriter> PartitionWriter::CreateBaseExtentWriter() {
- return std::make_unique<DirectExtentWriter>();
+ return std::make_unique<DirectExtentWriter>(target_fd_);
}
} // namespace chromeos_update_engine
diff --git a/payload_consumer/partition_writer_unittest.cc b/payload_consumer/partition_writer_unittest.cc
index 91e5e26..564d8d4 100644
--- a/payload_consumer/partition_writer_unittest.cc
+++ b/payload_consumer/partition_writer_unittest.cc
@@ -82,10 +82,10 @@
brillo::Blob PerformSourceCopyOp(const InstallOperation& op,
const brillo::Blob blob_data) {
ScopedTempFile source_partition("Blob-XXXXXX");
- DirectExtentWriter extent_writer;
FileDescriptorPtr fd(new EintrSafeFileDescriptor());
+ DirectExtentWriter extent_writer{fd};
EXPECT_TRUE(fd->Open(source_partition.path().c_str(), O_RDWR));
- EXPECT_TRUE(extent_writer.Init(fd, op.src_extents(), kBlockSize));
+ EXPECT_TRUE(extent_writer.Init(op.src_extents(), kBlockSize));
EXPECT_TRUE(extent_writer.Write(blob_data.data(), blob_data.size()));
ScopedTempFile target_partition("Blob-XXXXXX");
@@ -167,7 +167,7 @@
ASSERT_EQ(output_data, expected_data);
// Verify that the fake_fec was actually used.
- EXPECT_EQ(1U, fake_fec->GetReadOps().size());
+ EXPECT_GE(fake_fec->GetReadOps().size(), 1U);
EXPECT_EQ(1U, GetSourceEccRecoveredFailures());
}
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
index 8f2d674..051ccbf 100644
--- a/payload_consumer/postinstall_runner_action.cc
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -140,7 +140,7 @@
const InstallPlan::Partition& partition =
install_plan_.partitions[current_partition_];
- const string mountable_device = partition.postinstall_mount_device;
+ const string mountable_device = partition.readonly_target_path;
if (mountable_device.empty()) {
LOG(ERROR) << "Cannot make mountable device from " << partition.target_path;
return CompletePostinstall(ErrorCode::kPostinstallRunnerError);
@@ -383,6 +383,11 @@
}
}
+ auto dynamic_control = boot_control_->GetDynamicPartitionControl();
+ CHECK(dynamic_control);
+ dynamic_control->UnmapAllPartitions();
+ LOG(INFO) << "Unmapped all partitions.";
+
ScopedActionCompleter completer(processor_, this);
completer.set_code(error_code);
@@ -401,10 +406,6 @@
if (HasOutputPipe()) {
SetOutputObject(install_plan_);
}
- auto dynamic_control = boot_control_->GetDynamicPartitionControl();
- CHECK(dynamic_control);
- dynamic_control->UnmapAllPartitions();
- LOG(INFO) << "Unmapped all partitions.";
}
void PostinstallRunnerAction::SuspendAction() {
diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc
index 5ee2989..792ee28 100644
--- a/payload_consumer/postinstall_runner_action_unittest.cc
+++ b/payload_consumer/postinstall_runner_action_unittest.cc
@@ -195,7 +195,7 @@
InstallPlan::Partition part;
part.name = "part";
part.target_path = device_path;
- part.postinstall_mount_device = device_path;
+ part.readonly_target_path = device_path;
part.run_postinstall = true;
part.postinstall_path = postinstall_program;
InstallPlan install_plan;
@@ -360,7 +360,7 @@
InstallPlan::Partition part;
part.name = "part";
part.target_path = "/dev/null";
- part.postinstall_mount_device = "/dev/null";
+ part.readonly_target_path = "/dev/null";
part.run_postinstall = true;
part.postinstall_path = kPostinstallDefaultScript;
part.postinstall_optional = true;
diff --git a/payload_consumer/snapshot_extent_writer.cc b/payload_consumer/snapshot_extent_writer.cc
index c9e6f31..242e726 100644
--- a/payload_consumer/snapshot_extent_writer.cc
+++ b/payload_consumer/snapshot_extent_writer.cc
@@ -36,7 +36,6 @@
}
bool SnapshotExtentWriter::Init(
- FileDescriptorPtr /*fd*/,
const google::protobuf::RepeatedPtrField<Extent>& extents,
uint32_t block_size) {
extents_ = extents;
diff --git a/payload_consumer/snapshot_extent_writer.h b/payload_consumer/snapshot_extent_writer.h
index 6d9fe7d..c3a948e 100644
--- a/payload_consumer/snapshot_extent_writer.h
+++ b/payload_consumer/snapshot_extent_writer.h
@@ -14,6 +14,9 @@
// limitations under the License.
//
+#ifndef UPDATE_ENGINE_SNAPSHOT_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_SNAPSHOT_EXTENT_WRITER_H_
+
#include <cstdint>
#include <vector>
@@ -29,8 +32,7 @@
explicit SnapshotExtentWriter(android::snapshot::ICowWriter* cow_writer);
~SnapshotExtentWriter();
// Returns true on success.
- bool Init(FileDescriptorPtr fd,
- const google::protobuf::RepeatedPtrField<Extent>& extents,
+ bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents,
uint32_t block_size) override;
// Returns true on success.
// This will construct a COW_REPLACE operation and forward it to CowWriter. It
@@ -53,3 +55,5 @@
};
} // namespace chromeos_update_engine
+
+#endif
diff --git a/payload_consumer/snapshot_extent_writer_unittest.cc b/payload_consumer/snapshot_extent_writer_unittest.cc
index 0e22482..2201043 100644
--- a/payload_consumer/snapshot_extent_writer_unittest.cc
+++ b/payload_consumer/snapshot_extent_writer_unittest.cc
@@ -107,7 +107,7 @@
TEST_F(SnapshotExtentWriterTest, BufferWrites) {
google::protobuf::RepeatedPtrField<Extent> extents;
AddExtent(&extents, 123, 1);
- writer_.Init(nullptr, extents, kBlockSize);
+ writer_.Init(extents, kBlockSize);
std::vector<uint8_t> buf(kBlockSize, 0);
buf[123] = 231;
@@ -130,7 +130,7 @@
google::protobuf::RepeatedPtrField<Extent> extents;
AddExtent(&extents, 123, 1);
AddExtent(&extents, 125, 1);
- writer_.Init(nullptr, extents, kBlockSize);
+ writer_.Init(extents, kBlockSize);
std::vector<uint8_t> buf(kBlockSize * 2, 0);
buf[123] = 231;
@@ -153,7 +153,7 @@
google::protobuf::RepeatedPtrField<Extent> extents;
AddExtent(&extents, 123, 1);
AddExtent(&extents, 125, 2);
- writer_.Init(nullptr, extents, kBlockSize);
+ writer_.Init(extents, kBlockSize);
std::vector<uint8_t> buf(kBlockSize * 3);
std::memset(buf.data(), 0, buf.size());
diff --git a/payload_consumer/xz_extent_writer.cc b/payload_consumer/xz_extent_writer.cc
index a5b939d..a648351 100644
--- a/payload_consumer/xz_extent_writer.cc
+++ b/payload_consumer/xz_extent_writer.cc
@@ -57,12 +57,11 @@
TEST_AND_RETURN(input_buffer_.empty());
}
-bool XzExtentWriter::Init(FileDescriptorPtr fd,
- const RepeatedPtrField<Extent>& extents,
+bool XzExtentWriter::Init(const RepeatedPtrField<Extent>& extents,
uint32_t block_size) {
stream_ = xz_dec_init(XZ_DYNALLOC, kXzMaxDictSize);
TEST_AND_RETURN_FALSE(stream_ != nullptr);
- return underlying_writer_->Init(fd, extents, block_size);
+ return underlying_writer_->Init(extents, block_size);
}
bool XzExtentWriter::Write(const void* bytes, size_t count) {
diff --git a/payload_consumer/xz_extent_writer.h b/payload_consumer/xz_extent_writer.h
index e022274..70338f2 100644
--- a/payload_consumer/xz_extent_writer.h
+++ b/payload_consumer/xz_extent_writer.h
@@ -39,8 +39,7 @@
: underlying_writer_(std::move(underlying_writer)) {}
~XzExtentWriter() override;
- bool Init(FileDescriptorPtr fd,
- const google::protobuf::RepeatedPtrField<Extent>& extents,
+ bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents,
uint32_t block_size) override;
bool Write(const void* bytes, size_t count) override;
diff --git a/payload_consumer/xz_extent_writer_unittest.cc b/payload_consumer/xz_extent_writer_unittest.cc
index 34980a9..5269dbc 100644
--- a/payload_consumer/xz_extent_writer_unittest.cc
+++ b/payload_consumer/xz_extent_writer_unittest.cc
@@ -87,7 +87,7 @@
}
void WriteAll(const brillo::Blob& compressed) {
- EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+ EXPECT_TRUE(xz_writer_->Init({}, 1024));
EXPECT_TRUE(xz_writer_->Write(compressed.data(), compressed.size()));
EXPECT_TRUE(fake_extent_writer_->InitCalled());
@@ -130,7 +130,7 @@
}
TEST_F(XzExtentWriterTest, GarbageDataRejected) {
- EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+ EXPECT_TRUE(xz_writer_->Init({}, 1024));
// The sample_data_ is an uncompressed string.
EXPECT_FALSE(xz_writer_->Write(sample_data_.data(), sample_data_.size()));
}
@@ -138,7 +138,7 @@
TEST_F(XzExtentWriterTest, PartialDataIsKept) {
brillo::Blob compressed(std::begin(kCompressed30KiBofA),
std::end(kCompressed30KiBofA));
- EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+ EXPECT_TRUE(xz_writer_->Init({}, 1024));
for (uint8_t byte : compressed) {
EXPECT_TRUE(xz_writer_->Write(&byte, 1));
}
diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc
index d45de6a..2cd2ebc 100644
--- a/payload_generator/payload_generation_config.cc
+++ b/payload_generator/payload_generation_config.cc
@@ -23,6 +23,7 @@
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <brillo/strings/string_utils.h>
+#include <libsnapshot/cow_format.h>
#include "update_engine/common/utils.h"
#include "update_engine/payload_consumer/delta_performer.h"
@@ -185,6 +186,7 @@
// We use "gz" compression by default for VABC.
if (metadata->vabc_enabled()) {
metadata->set_vabc_compression_param("gz");
+ metadata->set_cow_version(android::snapshot::kCowVersionManifest);
}
dynamic_partition_metadata = std::move(metadata);
return true;
diff --git a/payload_generator/zip_unittest.cc b/payload_generator/zip_unittest.cc
index e357b15..10e899b 100644
--- a/payload_generator/zip_unittest.cc
+++ b/payload_generator/zip_unittest.cc
@@ -33,7 +33,6 @@
using chromeos_update_engine::test_utils::kRandomString;
using google::protobuf::RepeatedPtrField;
using std::string;
-using std::vector;
namespace chromeos_update_engine {
@@ -50,8 +49,7 @@
}
~MemoryExtentWriter() override = default;
- bool Init(FileDescriptorPtr fd,
- const RepeatedPtrField<Extent>& extents,
+ bool Init(const RepeatedPtrField<Extent>& extents,
uint32_t block_size) override {
return true;
}
@@ -72,7 +70,7 @@
std::unique_ptr<ExtentWriter> writer(
new W(std::make_unique<MemoryExtentWriter>(out)));
// Init() parameters are ignored by the testing MemoryExtentWriter.
- bool ok = writer->Init(nullptr, {}, 1);
+ bool ok = writer->Init({}, 1);
ok = writer->Write(in.data(), in.size()) && ok;
return ok;
}
diff --git a/scripts/ota_stress_test.py b/scripts/ota_stress_test.py
new file mode 100644
index 0000000..55aa4b1
--- /dev/null
+++ b/scripts/ota_stress_test.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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.
+#
+
+"""Repeatedly install an A/B update to an Android device over adb."""
+
+import argparse
+import sys
+from pathlib import Path
+import subprocess
+import signal
+
+
+def CleanupLoopDevices():
+ # b/184716804 clean up unused loop devices
+ subprocess.check_call(["adb", "shell", "su", "0", "losetup", '-D'])
+
+
+def CancelOTA():
+ subprocess.call(["adb", "shell", "su", "0",
+ "update_engine_client", "--cancel"])
+
+
+def PerformOTAThenPause(otafile: Path, update_device_script: Path):
+ python = sys.executable
+ ota_cmd = [python, str(update_device_script), str(otafile),
+ "--no-postinstall", "--no-slot-switch"]
+ p = subprocess.Popen(ota_cmd)
+ pid = p.pid
+ try:
+ ret = p.wait(10)
+ if ret is not None and ret != 0:
+ raise RuntimeError("OTA failed to apply")
+ if ret == 0:
+ print("OTA finished early? Surprise.")
+ return
+ except subprocess.TimeoutExpired:
+ pass
+ print(f"Killing {pid}")
+ subprocess.check_call(["pkill", "-INT", "-P", str(pid)])
+ p.send_signal(signal.SIGINT)
+ p.wait()
+
+
+def PerformTest(otafile: Path, resumes: int, timeout: int):
+ """Install an OTA to device, raising exceptions on failure
+
+ Args:
+ otafile: Path to the ota.zip to install
+
+ Return:
+ None if no error, if there's an error exception will be thrown
+ """
+ assert otafile.exists()
+ print("Applying", otafile)
+ script_dir = Path(__file__).parent.absolute()
+ update_device_script = script_dir / "update_device.py"
+ assert update_device_script.exists()
+ print(update_device_script)
+ python = sys.executable
+
+ for i in range(resumes):
+ print("Pause/Resume for the", i+1, "th time")
+ PerformOTAThenPause(otafile, update_device_script)
+ CancelOTA()
+ CleanupLoopDevices()
+
+ ota_cmd = [python, str(update_device_script),
+ str(otafile), "--no-postinstall"]
+ print("Finishing OTA Update", ota_cmd)
+ output = subprocess.check_output(
+ ota_cmd, stderr=subprocess.STDOUT, timeout=timeout).decode()
+ print(output)
+ if "onPayloadApplicationComplete(ErrorCode::kSuccess" not in output:
+ raise RuntimeError("Failed to finish OTA")
+ subprocess.call(
+ ["adb", "shell", "su", "0", "update_engine_client", "--cancel"])
+ subprocess.check_call(
+ ["adb", "shell", "su", "0", "update_engine_client", "--reset_status"])
+ CleanupLoopDevices()
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Android A/B OTA stress test helper.')
+ parser.add_argument('otafile', metavar='PAYLOAD', type=Path,
+ help='the OTA package file (a .zip file) or raw payload \
+ if device uses Omaha.')
+ parser.add_argument('-n', "--iterations", type=int, default=10,
+ metavar='ITERATIONS',
+ help='The number of iterations to run the stress test, or\
+ -1 to keep running until CTRL+C')
+ parser.add_argument('-r', "--resumes", type=int, default=5, metavar='RESUMES',
+ help='The number of iterations to pause the update when \
+ installing')
+ parser.add_argument('-t', "--timeout", type=int, default=60*60,
+ metavar='TIMEOUTS',
+ help='Timeout, in seconds, when waiting for OTA to \
+ finish')
+ args = parser.parse_args()
+ print(args)
+ n = args.iterations
+ while n != 0:
+ PerformTest(args.otafile, args.resumes, args.timeout)
+ n -= 1
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/update_device.py b/scripts/update_device.py
index b784b1b..f672cda 100755
--- a/scripts/update_device.py
+++ b/scripts/update_device.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
# Copyright (C) 2017 The Android Open Source Project
#
@@ -350,7 +350,7 @@
if self._device_serial:
self._command_prefix += ['-s', self._device_serial]
- def adb(self, command):
+ def adb(self, command, timeout_seconds: float = None):
"""Run an ADB command like "adb push".
Args:
@@ -365,7 +365,7 @@
command = self._command_prefix + command
logging.info('Running: %s', ' '.join(str(x) for x in command))
p = subprocess.Popen(command, universal_newlines=True)
- p.wait()
+ p.wait(timeout_seconds)
return p.returncode
def adb_output(self, command):
@@ -430,12 +430,12 @@
help='Do not perform slot switch after the update.')
parser.add_argument('--no-postinstall', action='store_true',
help='Do not execute postinstall scripts after the update.')
- parser.add_argument('--allocate_only', action='store_true',
+ parser.add_argument('--allocate-only', action='store_true',
help='Allocate space for this OTA, instead of actually \
applying the OTA.')
- parser.add_argument('--verify_only', action='store_true',
+ parser.add_argument('--verify-only', action='store_true',
help='Verify metadata then exit, instead of applying the OTA.')
- parser.add_argument('--no_care_map', action='store_true',
+ parser.add_argument('--no-care-map', action='store_true',
help='Do not push care_map.pb to device.')
args = parser.parse_args()
logging.basicConfig(
@@ -486,7 +486,7 @@
care_map_fp.write(zfp.read(CARE_MAP_ENTRY_NAME))
care_map_fp.flush()
dut.adb(["push", care_map_fp.name,
- "/data/ota_package/" + CARE_MAP_ENTRY_NAME])
+ "/data/ota_package/" + CARE_MAP_ENTRY_NAME])
if args.file:
# Update via pushing a file to /data.
@@ -546,7 +546,7 @@
if server_thread:
server_thread.StopServer()
for cmd in finalize_cmds:
- dut.adb(cmd)
+ dut.adb(cmd, 5)
return 0
diff --git a/update_metadata.proto b/update_metadata.proto
index bc9e34a..93e4e2e 100644
--- a/update_metadata.proto
+++ b/update_metadata.proto
@@ -359,6 +359,10 @@
// See system/core/fs_mgr/libsnapshot/cow_writer.cpp for available options,
// as this parameter is ultimated forwarded to libsnapshot's CowWriter
optional string vabc_compression_param = 4;
+
+ // COW version used by VABC. The represents the major version in the COW
+ // header
+ optional uint32 cow_version = 5;
}
// Definition has been duplicated from