diff options
184 files changed, 10234 insertions, 2736 deletions
diff --git a/.clang-format b/.clang-format index aed0ce8c..3044f598 100644 --- a/.clang-format +++ b/.clang-format @@ -34,6 +34,7 @@ BinPackArguments: false BinPackParameters: false CommentPragmas: NOLINT:.* DerivePointerAlignment: false +IncludeBlocks: Preserve PointerAlignment: Left TabWidth: 2 @@ -120,6 +120,7 @@ cc_defaults { "libbz", "libbspatch", "libbrotli", + "libc++fs", "libfec_rs", "libpuffpatch", "libverity_tree", @@ -128,6 +129,7 @@ cc_defaults { "libbase", "libcrypto", "libfec", + "libziparchive", ], } @@ -146,6 +148,7 @@ cc_library_static { "common/clock.cc", "common/constants.cc", "common/cpu_limiter.cc", + "common/dynamic_partition_control_stub.cc", "common/error_code_utils.cc", "common/file_fetcher.cc", "common/hash_calculator.cc", @@ -161,6 +164,7 @@ cc_library_static { "common/utils.cc", "payload_consumer/bzip_extent_writer.cc", "payload_consumer/cached_file_descriptor.cc", + "payload_consumer/certificate_parser_android.cc", "payload_consumer/delta_performer.cc", "payload_consumer/download_action.cc", "payload_consumer/extent_reader.cc", @@ -178,6 +182,7 @@ cc_library_static { "payload_consumer/verity_writer_android.cc", "payload_consumer/xz_extent_writer.cc", "payload_consumer/fec_file_descriptor.cc", + "payload_consumer/partition_update_generator_android.cc", ], } @@ -188,16 +193,41 @@ cc_defaults { name: "libupdate_engine_boot_control_exports", defaults: ["update_metadata-protos_exports"], - static_libs: ["update_metadata-protos"], + static_libs: [ + "libcutils", + "libfs_mgr_binder", + "libgsi", + "libpayload_consumer", + "libsnapshot", + "update_metadata-protos", + ], shared_libs: [ "libbootloader_message", - "libfs_mgr", - "libhwbinder", "libhidlbase", "liblp", + "libstatslog", "libutils", "android.hardware.boot@1.0", + "android.hardware.boot@1.1", + ], + header_libs: [ + "avb_headers", ], + target: { + recovery: { + static_libs: [ + "libfs_mgr", + "libsnapshot_nobinder", + ], + exclude_static_libs: [ + "libfs_mgr_binder", + "libsnapshot", + ], + exclude_shared_libs: [ + "libstatslog", + ], + }, + }, } cc_library_static { @@ -205,12 +235,15 @@ cc_library_static { defaults: [ "ue_defaults", "libupdate_engine_boot_control_exports", + "libpayload_consumer_exports", ], recovery_available: true, srcs: [ "boot_control_android.cc", + "cleanup_previous_update_action.cc", "dynamic_partition_control_android.cc", + "dynamic_partition_utils.cc", ], } @@ -227,6 +260,7 @@ cc_defaults { ], static_libs: [ + "libkver", "libpayload_consumer", "libupdate_engine_boot_control", ], @@ -239,9 +273,10 @@ cc_defaults { "libbrillo-binder", "libcurl", "libcutils", + "libupdate_engine_stable-cpp", "liblog", - "libmetricslogger", "libssl", + "libstatslog", "libutils", ], } @@ -265,12 +300,13 @@ cc_library_static { srcs: [ ":libupdate_engine_aidl", "binder_service_android.cc", + "binder_service_stable_android.cc", "certificate_checker.cc", "daemon_android.cc", "daemon_state_android.cc", "hardware_android.cc", "libcurl_http_fetcher.cc", - "metrics_reporter_android.cc", + "logging_android.cc", "metrics_utils.cc", "network_selector_android.cc", "update_attempter_android.cc", @@ -290,9 +326,12 @@ cc_binary { ], static_libs: ["libupdate_engine_android"], - required: ["cacerts_google"], + required: [ + "cacerts_google", + "otacerts", + ], - srcs: ["main.cc"], + srcs: ["main.cc", "metrics_reporter_android.cc"], init_rc: ["update_engine.rc"], } @@ -318,6 +357,7 @@ cc_binary { srcs: [ "hardware_android.cc", + "logging_android.cc", "metrics_reporter_stub.cc", "metrics_utils.cc", "network_selector_stub.cc", @@ -350,12 +390,12 @@ cc_binary { "libbrillo-stream", "libbrillo", "libchrome", + "libkver", ], target: { recovery: { exclude_shared_libs: [ "libprotobuf-cpp-lite", - "libhwbinder", "libbrillo-stream", "libbrillo", "libchrome", @@ -363,7 +403,9 @@ cc_binary { }, }, - required: ["android.hardware.boot@1.0-impl-wrapper.recovery"], + required: [ + "otacerts.recovery", + ], } // update_engine_client (type: executable) @@ -407,6 +449,9 @@ cc_defaults { "update_metadata-protos_exports", ], + header_libs: [ + "bootimg_headers", + ], static_libs: [ "libavb", "libbrotli", @@ -426,6 +471,20 @@ cc_defaults { } cc_library_static { + name: "libpayload_extent_ranges", + defaults: [ + "ue_defaults", + ], + host_supported: true, + srcs: [ + "payload_generator/extent_ranges.cc", + ], + static_libs: [ + "update_metadata-protos", + ], +} + +cc_library_static { name: "libpayload_generator", defaults: [ "ue_defaults", @@ -448,6 +507,7 @@ cc_library_static { "payload_generator/extent_utils.cc", "payload_generator/full_update_generator.cc", "payload_generator/mapfile_filesystem.cc", + "payload_generator/merge_sequence_generator.cc", "payload_generator/payload_file.cc", "payload_generator/payload_generation_config_android.cc", "payload_generator/payload_generation_config.cc", @@ -496,8 +556,6 @@ cc_test { gtest: false, stem: "delta_generator", - relative_install_path: "update_engine_unittests", - no_named_install_directory: true, } // test_http_server (type: executable) @@ -512,8 +570,6 @@ cc_test { ], gtest: false, - relative_install_path: "update_engine_unittests", - no_named_install_directory: true, } // test_subprocess (type: executable) @@ -525,8 +581,6 @@ cc_test { srcs: ["test_subprocess.cc"], gtest: false, - relative_install_path: "update_engine_unittests", - no_named_install_directory: true, } // Public keys for unittests. @@ -534,14 +588,20 @@ cc_test { genrule { name: "ue_unittest_keys", cmd: "openssl rsa -in $(location unittest_key.pem) -pubout -out $(location unittest_key.pub.pem) &&" + - "openssl rsa -in $(location unittest_key2.pem) -pubout -out $(location unittest_key2.pub.pem)", + "openssl rsa -in $(location unittest_key2.pem) -pubout -out $(location unittest_key2.pub.pem) &&" + + "openssl rsa -in $(location unittest_key_RSA4096.pem) -pubout -out $(location unittest_key_RSA4096.pub.pem) &&" + + "openssl pkey -in $(location unittest_key_EC.pem) -pubout -out $(location unittest_key_EC.pub.pem)", srcs: [ "unittest_key.pem", "unittest_key2.pem", + "unittest_key_RSA4096.pem", + "unittest_key_EC.pem", ], out: [ "unittest_key.pub.pem", "unittest_key2.pub.pem", + "unittest_key_RSA4096.pub.pem", + "unittest_key_EC.pub.pem", ], } @@ -571,11 +631,6 @@ cc_test { "libpayload_generator_exports", "libupdate_engine_android_exports", ], - required: [ - "test_http_server", - "test_subprocess", - "ue_unittest_delta_generator", - ], static_libs: [ "libpayload_generator", @@ -583,21 +638,33 @@ cc_test { "libgmock", "libchrome_test_helpers", "libupdate_engine_android", + "libdm", ], - shared_libs: [ - "libhidltransport", + + header_libs: [ + "libstorage_literals_headers", ], data: [ + ":test_http_server", + ":test_subprocess", + ":ue_unittest_delta_generator", ":ue_unittest_disk_imgs", ":ue_unittest_keys", + "otacerts.zip", "unittest_key.pem", "unittest_key2.pem", + "unittest_key_RSA4096.pem", + "unittest_key_EC.pem", "update_engine.conf", ], + // We cannot use the default generated AndroidTest.xml because of the use of helper modules + // (i.e. test_http_server, test_subprocess, ue_unittest_delta_generator). + test_config: "test_config.xml", + test_suites: ["device-tests"], + srcs: [ - "boot_control_android_unittest.cc", "certificate_checker_unittest.cc", "common/action_pipe_unittest.cc", "common/action_processor_unittest.cc", @@ -615,17 +682,22 @@ cc_test { "common/terminator_unittest.cc", "common/test_utils.cc", "common/utils_unittest.cc", + "dynamic_partition_control_android_unittest.cc", "libcurl_http_fetcher_unittest.cc", + "hardware_android_unittest.cc", "payload_consumer/bzip_extent_writer_unittest.cc", "payload_consumer/cached_file_descriptor_unittest.cc", + "payload_consumer/certificate_parser_android_unittest.cc", "payload_consumer/delta_performer_integration_test.cc", "payload_consumer/delta_performer_unittest.cc", + "payload_consumer/download_action_android_unittest.cc", "payload_consumer/extent_reader_unittest.cc", "payload_consumer/extent_writer_unittest.cc", "payload_consumer/fake_file_descriptor.cc", "payload_consumer/file_descriptor_utils_unittest.cc", "payload_consumer/file_writer_unittest.cc", "payload_consumer/filesystem_verifier_action_unittest.cc", + "payload_consumer/partition_update_generator_android_unittest.cc", "payload_consumer/postinstall_runner_action_unittest.cc", "payload_consumer/verity_writer_android_unittest.cc", "payload_consumer/xz_extent_writer_unittest.cc", @@ -641,6 +713,7 @@ cc_test { "payload_generator/fake_filesystem.cc", "payload_generator/full_update_generator_unittest.cc", "payload_generator/mapfile_filesystem_unittest.cc", + "payload_generator/merge_sequence_generator_unittest.cc", "payload_generator/payload_file_unittest.cc", "payload_generator/payload_generation_config_android_unittest.cc", "payload_generator/payload_generation_config_unittest.cc", @@ -651,17 +724,18 @@ cc_test { "testrunner.cc", "update_attempter_android_unittest.cc", "update_status_utils_unittest.cc", + "metrics_reporter_stub.cc", ], } // Brillo update payload generation script // ======================================================== -cc_prebuilt_binary { +sh_binary { name: "brillo_update_payload", device_supported: false, host_supported: true, - srcs: ["scripts/brillo_update_payload"], + src: "scripts/brillo_update_payload", required: [ "delta_generator", "shflags", @@ -674,3 +748,13 @@ cc_prebuilt_binary { }, }, } + +// update_engine header library +cc_library_headers { + name: "libupdate_engine_headers", + export_include_dirs: ["."], + apex_available: [ + "com.android.gki.*", + ], + host_supported: true, +} @@ -44,6 +44,7 @@ group("all") { ":test_subprocess", ":update_engine-test_images", ":update_engine-testkeys", + ":update_engine-testkeys-ec", ":update_engine_test_libs", ":update_engine_unittests", ] @@ -135,6 +136,7 @@ static_library("libpayload_consumer") { "common/clock.cc", "common/constants.cc", "common/cpu_limiter.cc", + "common/dynamic_partition_control_stub.cc", "common/error_code_utils.cc", "common/hash_calculator.cc", "common/http_common.cc", @@ -149,6 +151,7 @@ static_library("libpayload_consumer") { "common/utils.cc", "payload_consumer/bzip_extent_writer.cc", "payload_consumer/cached_file_descriptor.cc", + "payload_consumer/certificate_parser_stub.cc", "payload_consumer/delta_performer.cc", "payload_consumer/download_action.cc", "payload_consumer/extent_reader.cc", @@ -159,6 +162,7 @@ static_library("libpayload_consumer") { "payload_consumer/filesystem_verifier_action.cc", "payload_consumer/install_plan.cc", "payload_consumer/mount_history.cc", + "payload_consumer/partition_update_generator_stub.cc", "payload_consumer/payload_constants.cc", "payload_consumer/payload_metadata.cc", "payload_consumer/payload_verifier.cc", @@ -202,6 +206,7 @@ static_library("libupdate_engine") { "hardware_chromeos.cc", "image_properties_chromeos.cc", "libcurl_http_fetcher.cc", + "logging.cc", "metrics_reporter_omaha.cc", "metrics_utils.cc", "omaha_request_action.cc", @@ -335,7 +340,7 @@ static_library("libpayload_generator") { "payload_generator/annotated_operation.cc", "payload_generator/blob_file_writer.cc", "payload_generator/block_mapping.cc", - "payload_generator/boot_img_filesystem.cc", + "payload_generator/boot_img_filesystem_stub.cc", "payload_generator/bzip.cc", "payload_generator/deflate_utils.cc", "payload_generator/delta_diff_generator.cc", @@ -345,6 +350,7 @@ static_library("libpayload_generator") { "payload_generator/extent_utils.cc", "payload_generator/full_update_generator.cc", "payload_generator/mapfile_filesystem.cc", + "payload_generator/merge_sequence_generator.cc", "payload_generator/payload_file.cc", "payload_generator/payload_generation_config.cc", "payload_generator/payload_generation_config_chromeos.cc", @@ -414,10 +420,20 @@ if (use.test) { openssl_pem_out_dir = "include/update_engine" sources = [ "unittest_key.pem", + "unittest_key_RSA4096.pem", "unittest_key2.pem", ] } + genopenssl_key("update_engine-testkeys-ec") { + openssl_pem_in_dir = "." + openssl_pem_out_dir = "include/update_engine" + openssl_pem_algorithm = "ec" + sources = [ + "unittest_key_EC.pem", + ] + } + # Unpacks sample images used for testing. tar_bunzip2("update_engine-test_images") { image_out_dir = "." @@ -496,7 +512,6 @@ if (use.test) { "payload_generator/ab_generator_unittest.cc", "payload_generator/blob_file_writer_unittest.cc", "payload_generator/block_mapping_unittest.cc", - "payload_generator/boot_img_filesystem_unittest.cc", "payload_generator/deflate_utils_unittest.cc", "payload_generator/delta_diff_utils_unittest.cc", "payload_generator/ext2_filesystem_unittest.cc", @@ -504,6 +519,7 @@ if (use.test) { "payload_generator/extent_utils_unittest.cc", "payload_generator/full_update_generator_unittest.cc", "payload_generator/mapfile_filesystem_unittest.cc", + "payload_generator/merge_sequence_generator_unittest.cc", "payload_generator/payload_file_unittest.cc", "payload_generator/payload_generation_config_unittest.cc", "payload_generator/payload_properties_unittest.cc", diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 00000000..db31f86e --- /dev/null +++ b/Doxyfile @@ -0,0 +1,9 @@ +CLANG_DATABASE_PATH=../../ +HAVE_DOT=YES +CALL_GRAPH=YES +CALLER_GRAPH=YES +GENERATE_HTML=YES +GENERATE_LATEX=NO +INPUT=. +RECURSIVE=YES + diff --git a/METADATA b/METADATA new file mode 100644 index 00000000..d97975ca --- /dev/null +++ b/METADATA @@ -0,0 +1,3 @@ +third_party { + license_type: NOTICE +} @@ -2,7 +2,9 @@ set noparent # Android et. al. maintainers: deymo@google.com +elsk@google.com senj@google.com +xunchang@google.com # Chromium OS maintainers: ahassani@google.com diff --git a/binder_bindings/android/os/IUpdateEngine.aidl b/binder_bindings/android/os/IUpdateEngine.aidl index c0e29f50..c9580da4 100644 --- a/binder_bindings/android/os/IUpdateEngine.aidl +++ b/binder_bindings/android/os/IUpdateEngine.aidl @@ -17,6 +17,7 @@ package android.os; import android.os.IUpdateEngineCallback; +import android.os.ParcelFileDescriptor; /** @hide */ interface IUpdateEngine { @@ -26,6 +27,11 @@ interface IUpdateEngine { in long payload_size, in String[] headerKeyValuePairs); /** @hide */ + void applyPayloadFd(in ParcelFileDescriptor pfd, + in long payload_offset, + in long payload_size, + in String[] headerKeyValuePairs); + /** @hide */ boolean bind(IUpdateEngineCallback callback); /** @hide */ boolean unbind(IUpdateEngineCallback callback); @@ -39,4 +45,30 @@ interface IUpdateEngine { void resetStatus(); /** @hide */ boolean verifyPayloadApplicable(in String metadataFilename); + /** + * Allocate space on userdata partition. + * + * @return 0 indicates allocation is successful. + * Non-zero indicates space is insufficient. The returned value is the + * total required space (in bytes) on userdata partition. + * + * @throws ServiceSpecificException for other errors. + * + * @hide + */ + long allocateSpaceForPayload(in String metadataFilename, + in String[] headerKeyValuePairs); + /** @hide + * + * Wait for merge to finish, and clean up necessary files. + * + * @param callback Report status updates in callback (not the one previously + * bound with {@link #bind()}). + * {@link IUpdateEngineCallback#onStatusUpdate} is called with + * CLEANUP_PREVIOUS_UPDATE and a progress value during the cleanup. + * {@link IUpdateEngineCallback#onPayloadApplicationComplete} is called at + * the end with SUCCESS if successful. ERROR if transient errors (e.g. merged + * but needs reboot). DEVICE_CORRUPTED for permanent errors. + */ + void cleanupSuccessfulUpdate(IUpdateEngineCallback callback); } diff --git a/binder_bindings/android/os/IUpdateEngineCallback.aidl b/binder_bindings/android/os/IUpdateEngineCallback.aidl index ee15c8b0..4bacf9a6 100644 --- a/binder_bindings/android/os/IUpdateEngineCallback.aidl +++ b/binder_bindings/android/os/IUpdateEngineCallback.aidl @@ -19,6 +19,7 @@ package android.os; /** @hide */ oneway interface IUpdateEngineCallback { /** @hide */ + @UnsupportedAppUsage void onStatusUpdate(int status_code, float percentage); /** @hide */ void onPayloadApplicationComplete(int error_code); diff --git a/binder_service_android.cc b/binder_service_android.cc index 137694a7..0c8bc2f4 100644 --- a/binder_service_android.cc +++ b/binder_service_android.cc @@ -16,23 +16,23 @@ #include "update_engine/binder_service_android.h" +#include <memory> + #include <base/bind.h> #include <base/logging.h> #include <binderwrapper/binder_wrapper.h> #include <brillo/errors/error.h> #include <utils/String8.h> +#include "update_engine/binder_service_android_common.h" + using android::binder::Status; using android::os::IUpdateEngineCallback; +using android::os::ParcelFileDescriptor; +using std::string; +using std::vector; using update_engine::UpdateEngineStatus; -namespace { -Status ErrorPtrToStatus(const brillo::ErrorPtr& error) { - return Status::fromServiceSpecificError( - 1, android::String8{error->GetMessage().c_str()}); -} -} // namespace - namespace chromeos_update_engine { BinderUpdateEngineAndroidService::BinderUpdateEngineAndroidService( @@ -57,6 +57,20 @@ void BinderUpdateEngineAndroidService::SendPayloadApplicationComplete( Status BinderUpdateEngineAndroidService::bind( const android::sp<IUpdateEngineCallback>& callback, bool* return_value) { + // Send an status update on connection (except when no update sent so far). + // Even though the status update is oneway, it still returns an erroneous + // status in case of a selinux denial. We should at least check this status + // and fails the binding. + if (last_status_ != -1) { + auto status = callback->onStatusUpdate(last_status_, last_progress_); + if (!status.isOk()) { + LOG(ERROR) << "Failed to call onStatusUpdate() from callback: " + << status.toString8(); + *return_value = false; + return Status::ok(); + } + } + callbacks_.emplace_back(callback); const android::sp<IBinder>& callback_binder = @@ -69,12 +83,6 @@ Status BinderUpdateEngineAndroidService::bind( base::Unretained(this), base::Unretained(callback_binder.get()))); - // Send an status update on connection (except when no update sent so far), - // since the status update is oneway and we don't need to wait for the - // response. - if (last_status_ != -1) - callback->onStatusUpdate(last_status_, last_progress_); - *return_value = true; return Status::ok(); } @@ -94,13 +102,9 @@ Status BinderUpdateEngineAndroidService::applyPayload( const android::String16& url, int64_t payload_offset, int64_t payload_size, - const std::vector<android::String16>& header_kv_pairs) { - const std::string payload_url{android::String8{url}.string()}; - std::vector<std::string> str_headers; - str_headers.reserve(header_kv_pairs.size()); - for (const auto& header : header_kv_pairs) { - str_headers.emplace_back(android::String8{header}.string()); - } + const vector<android::String16>& header_kv_pairs) { + const string payload_url{android::String8{url}.string()}; + vector<string> str_headers = ToVecString(header_kv_pairs); brillo::ErrorPtr error; if (!service_delegate_->ApplyPayload( @@ -110,6 +114,21 @@ Status BinderUpdateEngineAndroidService::applyPayload( return Status::ok(); } +Status BinderUpdateEngineAndroidService::applyPayloadFd( + const ParcelFileDescriptor& pfd, + int64_t payload_offset, + int64_t payload_size, + const vector<android::String16>& header_kv_pairs) { + vector<string> str_headers = ToVecString(header_kv_pairs); + + brillo::ErrorPtr error; + if (!service_delegate_->ApplyPayload( + pfd.get(), payload_offset, payload_size, str_headers, &error)) { + return ErrorPtrToStatus(error); + } + return Status::ok(); +} + Status BinderUpdateEngineAndroidService::suspend() { brillo::ErrorPtr error; if (!service_delegate_->SuspendUpdate(&error)) @@ -167,4 +186,58 @@ bool BinderUpdateEngineAndroidService::UnbindCallback(const IBinder* callback) { return true; } +Status BinderUpdateEngineAndroidService::allocateSpaceForPayload( + const android::String16& metadata_filename, + const vector<android::String16>& header_kv_pairs, + int64_t* return_value) { + const std::string payload_metadata{ + android::String8{metadata_filename}.string()}; + vector<string> str_headers = ToVecString(header_kv_pairs); + LOG(INFO) << "Received a request of allocating space for " << payload_metadata + << "."; + brillo::ErrorPtr error; + *return_value = + static_cast<int64_t>(service_delegate_->AllocateSpaceForPayload( + payload_metadata, str_headers, &error)); + if (error != nullptr) + return ErrorPtrToStatus(error); + return Status::ok(); +} + +class CleanupSuccessfulUpdateCallback + : public CleanupSuccessfulUpdateCallbackInterface { + public: + CleanupSuccessfulUpdateCallback( + const android::sp<IUpdateEngineCallback>& callback) + : callback_(callback) {} + void OnCleanupComplete(int32_t error_code) { + ignore_result(callback_->onPayloadApplicationComplete(error_code)); + } + void OnCleanupProgressUpdate(double progress) { + ignore_result(callback_->onStatusUpdate( + static_cast<int32_t>( + update_engine::UpdateStatus::CLEANUP_PREVIOUS_UPDATE), + progress)); + } + void RegisterForDeathNotifications(base::Closure unbind) { + const android::sp<android::IBinder>& callback_binder = + IUpdateEngineCallback::asBinder(callback_); + auto binder_wrapper = android::BinderWrapper::Get(); + binder_wrapper->RegisterForDeathNotifications(callback_binder, unbind); + } + + private: + android::sp<IUpdateEngineCallback> callback_; +}; + +Status BinderUpdateEngineAndroidService::cleanupSuccessfulUpdate( + const android::sp<IUpdateEngineCallback>& callback) { + brillo::ErrorPtr error; + service_delegate_->CleanupSuccessfulUpdate( + std::make_unique<CleanupSuccessfulUpdateCallback>(callback), &error); + if (error != nullptr) + return ErrorPtrToStatus(error); + return Status::ok(); +} + } // namespace chromeos_update_engine diff --git a/binder_service_android.h b/binder_service_android.h index d8c4e9c3..5f282252 100644 --- a/binder_service_android.h +++ b/binder_service_android.h @@ -53,6 +53,11 @@ class BinderUpdateEngineAndroidService : public android::os::BnUpdateEngine, int64_t payload_offset, int64_t payload_size, const std::vector<android::String16>& header_kv_pairs) override; + android::binder::Status applyPayloadFd( + const ::android::os::ParcelFileDescriptor& pfd, + int64_t payload_offset, + int64_t payload_size, + const std::vector<android::String16>& header_kv_pairs) override; android::binder::Status bind( const android::sp<android::os::IUpdateEngineCallback>& callback, bool* return_value) override; @@ -65,6 +70,12 @@ class BinderUpdateEngineAndroidService : public android::os::BnUpdateEngine, android::binder::Status resetStatus() override; android::binder::Status verifyPayloadApplicable( const android::String16& metadata_filename, bool* return_value) override; + android::binder::Status allocateSpaceForPayload( + const android::String16& metadata_filename, + const std::vector<android::String16>& header_kv_pairs, + int64_t* return_value) override; + android::binder::Status cleanupSuccessfulUpdate( + const android::sp<android::os::IUpdateEngineCallback>& callback) override; private: // Remove the passed |callback| from the list of registered callbacks. Called diff --git a/binder_service_android_common.h b/binder_service_android_common.h new file mode 100644 index 00000000..fc621d97 --- /dev/null +++ b/binder_service_android_common.h @@ -0,0 +1,45 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_BINDER_SERVICE_ANDROID_COMMON_H_ +#define UPDATE_ENGINE_BINDER_SERVICE_ANDROID_COMMON_H_ + +#include <string> +#include <vector> + +#include <binder/Status.h> + +namespace chromeos_update_engine { + +static inline android::binder::Status ErrorPtrToStatus( + const brillo::ErrorPtr& error) { + return android::binder::Status::fromServiceSpecificError( + 1, android::String8{error->GetMessage().c_str()}); +} + +static inline std::vector<std::string> ToVecString( + const std::vector<android::String16>& inp) { + std::vector<std::string> out; + out.reserve(inp.size()); + for (const auto& e : inp) { + out.emplace_back(android::String8{e}.string()); + } + return out; +} + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_BINDER_SERVICE_ANDROID_COMMON_H_ diff --git a/binder_service_stable_android.cc b/binder_service_stable_android.cc new file mode 100644 index 00000000..a12b349b --- /dev/null +++ b/binder_service_stable_android.cc @@ -0,0 +1,132 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/binder_service_stable_android.h" + +#include <memory> + +#include <base/bind.h> +#include <base/logging.h> +#include <binderwrapper/binder_wrapper.h> +#include <brillo/errors/error.h> +#include <utils/String8.h> + +#include "update_engine/binder_service_android_common.h" + +using android::binder::Status; +using android::os::IUpdateEngineStableCallback; +using android::os::ParcelFileDescriptor; +using std::string; +using std::vector; +using update_engine::UpdateEngineStatus; + +namespace chromeos_update_engine { + +BinderUpdateEngineAndroidStableService::BinderUpdateEngineAndroidStableService( + ServiceDelegateAndroidInterface* service_delegate) + : service_delegate_(service_delegate) {} + +void BinderUpdateEngineAndroidStableService::SendStatusUpdate( + const UpdateEngineStatus& update_engine_status) { + last_status_ = static_cast<int>(update_engine_status.status); + last_progress_ = update_engine_status.progress; + if (callback_) { + callback_->onStatusUpdate(last_status_, last_progress_); + } +} + +void BinderUpdateEngineAndroidStableService::SendPayloadApplicationComplete( + ErrorCode error_code) { + if (callback_) { + callback_->onPayloadApplicationComplete(static_cast<int>(error_code)); + } +} + +Status BinderUpdateEngineAndroidStableService::bind( + const android::sp<IUpdateEngineStableCallback>& callback, + bool* return_value) { + // Reject binding if another callback is already bound. + if (callback_ != nullptr) { + LOG(ERROR) << "Another callback is already bound. Can't bind new callback."; + *return_value = false; + return Status::ok(); + } + + // See BinderUpdateEngineAndroidService::bind. + if (last_status_ != -1) { + auto status = callback->onStatusUpdate(last_status_, last_progress_); + if (!status.isOk()) { + LOG(ERROR) << "Failed to call onStatusUpdate() from callback: " + << status.toString8(); + *return_value = false; + return Status::ok(); + } + } + + callback_ = callback; + + const android::sp<IBinder>& callback_binder = + IUpdateEngineStableCallback::asBinder(callback); + auto binder_wrapper = android::BinderWrapper::Get(); + binder_wrapper->RegisterForDeathNotifications( + callback_binder, + base::Bind(base::IgnoreResult( + &BinderUpdateEngineAndroidStableService::UnbindCallback), + base::Unretained(this), + base::Unretained(callback_binder.get()))); + + *return_value = true; + return Status::ok(); +} + +Status BinderUpdateEngineAndroidStableService::unbind( + const android::sp<IUpdateEngineStableCallback>& callback, + bool* return_value) { + const android::sp<IBinder>& callback_binder = + IUpdateEngineStableCallback::asBinder(callback); + auto binder_wrapper = android::BinderWrapper::Get(); + binder_wrapper->UnregisterForDeathNotifications(callback_binder); + + *return_value = UnbindCallback(callback_binder.get()); + return Status::ok(); +} + +Status BinderUpdateEngineAndroidStableService::applyPayloadFd( + const ParcelFileDescriptor& pfd, + int64_t payload_offset, + int64_t payload_size, + const vector<android::String16>& header_kv_pairs) { + vector<string> str_headers = ToVecString(header_kv_pairs); + + brillo::ErrorPtr error; + if (!service_delegate_->ApplyPayload( + pfd.get(), payload_offset, payload_size, str_headers, &error)) { + return ErrorPtrToStatus(error); + } + return Status::ok(); +} + +bool BinderUpdateEngineAndroidStableService::UnbindCallback( + const IBinder* callback) { + if (IUpdateEngineStableCallback::asBinder(callback_).get() != callback) { + LOG(ERROR) << "Unable to unbind unknown callback."; + return false; + } + callback_ = nullptr; + return true; +} + +} // namespace chromeos_update_engine diff --git a/binder_service_stable_android.h b/binder_service_stable_android.h new file mode 100644 index 00000000..16677980 --- /dev/null +++ b/binder_service_stable_android.h @@ -0,0 +1,85 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_BINDER_SERVICE_STABLE_ANDROID_H_ +#define UPDATE_ENGINE_BINDER_SERVICE_STABLE_ANDROID_H_ + +#include <stdint.h> + +#include <string> +#include <vector> + +#include <utils/Errors.h> +#include <utils/String16.h> +#include <utils/StrongPointer.h> + +#include "android/os/BnUpdateEngineStable.h" +#include "android/os/IUpdateEngineStableCallback.h" +#include "update_engine/service_delegate_android_interface.h" +#include "update_engine/service_observer_interface.h" + +namespace chromeos_update_engine { + +class BinderUpdateEngineAndroidStableService + : public android::os::BnUpdateEngineStable, + public ServiceObserverInterface { + public: + explicit BinderUpdateEngineAndroidStableService( + ServiceDelegateAndroidInterface* service_delegate); + ~BinderUpdateEngineAndroidStableService() override = default; + + const char* ServiceName() const { + return "android.os.UpdateEngineStableService"; + } + + // ServiceObserverInterface overrides. + void SendStatusUpdate( + const update_engine::UpdateEngineStatus& update_engine_status) override; + void SendPayloadApplicationComplete(ErrorCode error_code) override; + + // android::os::BnUpdateEngineStable overrides. + android::binder::Status applyPayloadFd( + const ::android::os::ParcelFileDescriptor& pfd, + int64_t payload_offset, + int64_t payload_size, + const std::vector<android::String16>& header_kv_pairs) override; + android::binder::Status bind( + const android::sp<android::os::IUpdateEngineStableCallback>& callback, + bool* return_value) override; + android::binder::Status unbind( + const android::sp<android::os::IUpdateEngineStableCallback>& callback, + bool* return_value) override; + + private: + // Remove the passed |callback| from the list of registered callbacks. Called + // on unbind() or whenever the callback object is destroyed. + // Returns true on success. + bool UnbindCallback(const IBinder* callback); + + // Bound callback. The stable interface only supports one callback at a time. + android::sp<android::os::IUpdateEngineStableCallback> callback_; + + // Cached copy of the last status update sent. Used to send an initial + // notification when bind() is called from the client. + int last_status_{-1}; + double last_progress_{0.0}; + + ServiceDelegateAndroidInterface* service_delegate_; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_BINDER_SERVICE_STABLE_ANDROID_H_ diff --git a/boot_control_android.cc b/boot_control_android.cc index 8909cd90..dee5fa84 100644 --- a/boot_control_android.cc +++ b/boot_control_android.cc @@ -22,11 +22,8 @@ #include <base/bind.h> #include <base/logging.h> -#include <base/strings/string_util.h> #include <bootloader_message/bootloader_message.h> #include <brillo/message_loops/message_loop.h> -#include <fs_mgr.h> -#include <fs_mgr_overlayfs.h> #include "update_engine/common/utils.h" #include "update_engine/dynamic_partition_control_android.h" @@ -34,15 +31,12 @@ using std::string; using android::dm::DmDeviceState; -using android::fs_mgr::Partition; using android::hardware::hidl_string; using android::hardware::Return; using android::hardware::boot::V1_0::BoolResult; using android::hardware::boot::V1_0::CommandResult; using android::hardware::boot::V1_0::IBootControl; using Slot = chromeos_update_engine::BootControlInterface::Slot; -using PartitionMetadata = - chromeos_update_engine::BootControlInterface::PartitionMetadata; namespace { @@ -80,10 +74,6 @@ bool BootControlAndroid::Init() { return true; } -void BootControlAndroid::Cleanup() { - dynamic_control_->Cleanup(); -} - unsigned int BootControlAndroid::GetNumSlots() const { return module_->getNumberSlots(); } @@ -92,140 +82,24 @@ BootControlInterface::Slot BootControlAndroid::GetCurrentSlot() const { return module_->getCurrentSlot(); } -bool BootControlAndroid::GetSuffix(Slot slot, string* suffix) const { - auto store_suffix_cb = [&suffix](hidl_string cb_suffix) { - *suffix = cb_suffix.c_str(); - }; - Return<void> ret = module_->getSuffix(slot, store_suffix_cb); - - if (!ret.isOk()) { - LOG(ERROR) << "boot_control impl returned no suffix for slot " - << SlotName(slot); - return false; - } - return true; -} - -bool BootControlAndroid::IsSuperBlockDevice( - const base::FilePath& device_dir, - Slot slot, - const string& partition_name_suffix) const { - string source_device = - device_dir.Append(fs_mgr_get_super_partition_name(slot)).value(); - auto source_metadata = dynamic_control_->LoadMetadataBuilder( - source_device, slot, BootControlInterface::kInvalidSlot); - return source_metadata->HasBlockDevice(partition_name_suffix); -} - -BootControlAndroid::DynamicPartitionDeviceStatus -BootControlAndroid::GetDynamicPartitionDevice( - const base::FilePath& device_dir, - const string& partition_name_suffix, - Slot slot, - string* device) const { - string super_device = - device_dir.Append(fs_mgr_get_super_partition_name(slot)).value(); - - auto builder = dynamic_control_->LoadMetadataBuilder( - super_device, slot, BootControlInterface::kInvalidSlot); - - if (builder == nullptr) { - LOG(ERROR) << "No metadata in slot " - << BootControlInterface::SlotName(slot); - return DynamicPartitionDeviceStatus::ERROR; - } - - if (builder->FindPartition(partition_name_suffix) == nullptr) { - LOG(INFO) << partition_name_suffix - << " is not in super partition metadata."; - - Slot current_slot = GetCurrentSlot(); - if (IsSuperBlockDevice(device_dir, current_slot, partition_name_suffix)) { - LOG(ERROR) << "The static partition " << partition_name_suffix - << " is a block device for current metadata (" - << fs_mgr_get_super_partition_name(current_slot) << ", slot " - << BootControlInterface::SlotName(current_slot) - << "). It cannot be used as a logical partition."; - return DynamicPartitionDeviceStatus::ERROR; - } - - return DynamicPartitionDeviceStatus::TRY_STATIC; - } - - DmDeviceState state = dynamic_control_->GetState(partition_name_suffix); - - // Device is mapped in the previous GetPartitionDevice() call. Just return - // the path. - if (state == DmDeviceState::ACTIVE) { - if (dynamic_control_->GetDmDevicePathByName(partition_name_suffix, - device)) { - LOG(INFO) << partition_name_suffix - << " is mapped on device mapper: " << *device; - return DynamicPartitionDeviceStatus::SUCCESS; - } - LOG(ERROR) << partition_name_suffix << " is mapped but path is unknown."; - return DynamicPartitionDeviceStatus::ERROR; - } - - if (state == DmDeviceState::INVALID) { - bool force_writable = slot != GetCurrentSlot(); - if (dynamic_control_->MapPartitionOnDeviceMapper(super_device, - partition_name_suffix, - slot, - force_writable, - device)) { - return DynamicPartitionDeviceStatus::SUCCESS; - } - return DynamicPartitionDeviceStatus::ERROR; - } - - LOG(ERROR) << partition_name_suffix - << " is mapped on device mapper but state is unknown: " - << static_cast<std::underlying_type_t<DmDeviceState>>(state); - return DynamicPartitionDeviceStatus::ERROR; +bool BootControlAndroid::GetPartitionDevice(const std::string& partition_name, + BootControlInterface::Slot slot, + bool not_in_payload, + std::string* device, + bool* is_dynamic) const { + return dynamic_control_->GetPartitionDevice(partition_name, + slot, + GetCurrentSlot(), + not_in_payload, + device, + is_dynamic); } bool BootControlAndroid::GetPartitionDevice(const string& partition_name, - Slot slot, + BootControlInterface::Slot slot, string* device) const { - string suffix; - if (!GetSuffix(slot, &suffix)) { - return false; - } - const string partition_name_suffix = partition_name + suffix; - - string device_dir_str; - if (!dynamic_control_->GetDeviceDir(&device_dir_str)) { - return false; - } - base::FilePath device_dir(device_dir_str); - - // When looking up target partition devices, treat them as static if the - // current payload doesn't encode them as dynamic partitions. This may happen - // when applying a retrofit update on top of a dynamic-partitions-enabled - // build. - if (dynamic_control_->IsDynamicPartitionsEnabled() && - (slot == GetCurrentSlot() || is_target_dynamic_)) { - switch (GetDynamicPartitionDevice( - device_dir, partition_name_suffix, slot, device)) { - case DynamicPartitionDeviceStatus::SUCCESS: - return true; - case DynamicPartitionDeviceStatus::TRY_STATIC: - break; - case DynamicPartitionDeviceStatus::ERROR: // fallthrough - default: - return false; - } - } - - base::FilePath path = device_dir.Append(partition_name_suffix); - if (!dynamic_control_->DeviceExists(path.value())) { - LOG(ERROR) << "Device file " << path.value() << " does not exist."; - return false; - } - - *device = path.value(); - return true; + return GetPartitionDevice( + partition_name, slot, false /* not_in_payload */, device, nullptr); } bool BootControlAndroid::IsSlotBootable(Slot slot) const { @@ -288,160 +162,25 @@ bool BootControlAndroid::MarkBootSuccessfulAsync( brillo::MessageLoop::kTaskIdNull; } -namespace { - -bool UpdatePartitionMetadata(DynamicPartitionControlInterface* dynamic_control, - Slot source_slot, - Slot target_slot, - const string& target_suffix, - const PartitionMetadata& partition_metadata) { - string device_dir_str; - if (!dynamic_control->GetDeviceDir(&device_dir_str)) { - return false; - } - base::FilePath device_dir(device_dir_str); - auto source_device = - device_dir.Append(fs_mgr_get_super_partition_name(source_slot)).value(); - - auto builder = dynamic_control->LoadMetadataBuilder( - source_device, source_slot, target_slot); - if (builder == nullptr) { - // TODO(elsk): allow reconstructing metadata from partition_metadata - // in recovery sideload. - LOG(ERROR) << "No metadata at " - << BootControlInterface::SlotName(source_slot); +bool BootControlAndroid::IsSlotMarkedSuccessful( + BootControlInterface::Slot slot) const { + Return<BoolResult> ret = module_->isSlotMarkedSuccessful(slot); + CommandResult result; + if (!ret.isOk()) { + LOG(ERROR) << "Unable to determine if slot " << SlotName(slot) + << " is marked successful: " << ret.description(); return false; } - - std::vector<string> groups = builder->ListGroups(); - for (const auto& group_name : groups) { - if (base::EndsWith( - group_name, target_suffix, base::CompareCase::SENSITIVE)) { - LOG(INFO) << "Removing group " << group_name; - builder->RemoveGroupAndPartitions(group_name); - } - } - - uint64_t total_size = 0; - for (const auto& group : partition_metadata.groups) { - total_size += group.size; - } - - string expr; - uint64_t allocatable_space = builder->AllocatableSpace(); - if (!dynamic_control->IsDynamicPartitionsRetrofit()) { - allocatable_space /= 2; - expr = "half of "; - } - if (total_size > allocatable_space) { - LOG(ERROR) << "The maximum size of all groups with suffix " << target_suffix - << " (" << total_size << ") has exceeded " << expr - << " allocatable space for dynamic partitions " - << allocatable_space << "."; + if (ret == BoolResult::INVALID_SLOT) { + LOG(ERROR) << "Invalid slot: " << SlotName(slot); return false; } - - for (const auto& group : partition_metadata.groups) { - auto group_name_suffix = group.name + target_suffix; - if (!builder->AddGroup(group_name_suffix, group.size)) { - LOG(ERROR) << "Cannot add group " << group_name_suffix << " with size " - << group.size; - return false; - } - LOG(INFO) << "Added group " << group_name_suffix << " with size " - << group.size; - - for (const auto& partition : group.partitions) { - auto partition_name_suffix = partition.name + target_suffix; - Partition* p = builder->AddPartition( - partition_name_suffix, group_name_suffix, LP_PARTITION_ATTR_READONLY); - if (!p) { - LOG(ERROR) << "Cannot add partition " << partition_name_suffix - << " to group " << group_name_suffix; - return false; - } - if (!builder->ResizePartition(p, partition.size)) { - LOG(ERROR) << "Cannot resize partition " << partition_name_suffix - << " to size " << partition.size << ". Not enough space?"; - return false; - } - LOG(INFO) << "Added partition " << partition_name_suffix << " to group " - << group_name_suffix << " with size " << partition.size; - } - } - - auto target_device = - device_dir.Append(fs_mgr_get_super_partition_name(target_slot)).value(); - return dynamic_control->StoreMetadata( - target_device, builder.get(), target_slot); -} - -bool UnmapTargetPartitions(DynamicPartitionControlInterface* dynamic_control, - const string& target_suffix, - const PartitionMetadata& partition_metadata) { - for (const auto& group : partition_metadata.groups) { - for (const auto& partition : group.partitions) { - if (!dynamic_control->UnmapPartitionOnDeviceMapper( - partition.name + target_suffix, true /* wait */)) { - return false; - } - } - } - return true; + return ret == BoolResult::TRUE; } -} // namespace - -bool BootControlAndroid::InitPartitionMetadata( - Slot target_slot, - const PartitionMetadata& partition_metadata, - bool update_metadata) { - if (fs_mgr_overlayfs_is_setup()) { - // Non DAP devices can use overlayfs as well. - LOG(WARNING) - << "overlayfs overrides are active and can interfere with our " - "resources.\n" - << "run adb enable-verity to deactivate if required and try again."; - } - if (!dynamic_control_->IsDynamicPartitionsEnabled()) { - return true; - } - - auto source_slot = GetCurrentSlot(); - if (target_slot == source_slot) { - LOG(ERROR) << "Cannot call InitPartitionMetadata on current slot."; - return false; - } - - // Although the current build supports dynamic partitions, the given payload - // doesn't use it for target partitions. This could happen when applying a - // retrofit update. Skip updating the partition metadata for the target slot. - is_target_dynamic_ = !partition_metadata.groups.empty(); - if (!is_target_dynamic_) { - return true; - } - - if (!update_metadata) { - return true; - } - - string target_suffix; - if (!GetSuffix(target_slot, &target_suffix)) { - return false; - } - - // Unmap all the target dynamic partitions because they would become - // inconsistent with the new metadata. - if (!UnmapTargetPartitions( - dynamic_control_.get(), target_suffix, partition_metadata)) { - return false; - } - - return UpdatePartitionMetadata(dynamic_control_.get(), - source_slot, - target_slot, - target_suffix, - partition_metadata); +DynamicPartitionControlInterface* +BootControlAndroid::GetDynamicPartitionControl() { + return dynamic_control_.get(); } } // namespace chromeos_update_engine diff --git a/boot_control_android.h b/boot_control_android.h index a6f33bed..5009dbd3 100644 --- a/boot_control_android.h +++ b/boot_control_android.h @@ -22,11 +22,11 @@ #include <string> #include <android/hardware/boot/1.0/IBootControl.h> -#include <base/files/file_util.h> #include <liblp/builder.h> #include "update_engine/common/boot_control.h" -#include "update_engine/dynamic_partition_control_interface.h" +#include "update_engine/common/dynamic_partition_control_interface.h" +#include "update_engine/dynamic_partition_control_android.h" namespace chromeos_update_engine { @@ -46,47 +46,25 @@ class BootControlAndroid : public BootControlInterface { BootControlInterface::Slot GetCurrentSlot() const override; bool GetPartitionDevice(const std::string& partition_name, BootControlInterface::Slot slot, + bool not_in_payload, + std::string* device, + bool* is_dynamic) const override; + bool GetPartitionDevice(const std::string& partition_name, + BootControlInterface::Slot slot, std::string* device) const override; bool IsSlotBootable(BootControlInterface::Slot slot) const override; bool MarkSlotUnbootable(BootControlInterface::Slot slot) override; bool SetActiveBootSlot(BootControlInterface::Slot slot) override; bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override; - bool InitPartitionMetadata(Slot slot, - const PartitionMetadata& partition_metadata, - bool update_metadata) override; - void Cleanup() override; + bool IsSlotMarkedSuccessful(BootControlInterface::Slot slot) const override; + DynamicPartitionControlInterface* GetDynamicPartitionControl() override; private: ::android::sp<::android::hardware::boot::V1_0::IBootControl> module_; - std::unique_ptr<DynamicPartitionControlInterface> dynamic_control_; + std::unique_ptr<DynamicPartitionControlAndroid> dynamic_control_; friend class BootControlAndroidTest; - // Wrapper method of IBootControl::getSuffix(). - bool GetSuffix(Slot slot, std::string* out) const; - - enum class DynamicPartitionDeviceStatus { - SUCCESS, - ERROR, - TRY_STATIC, - }; - - DynamicPartitionDeviceStatus GetDynamicPartitionDevice( - const base::FilePath& device_dir, - const std::string& partition_name_suffix, - Slot slot, - std::string* device) const; - - // Return true if |partition_name_suffix| is a block device of - // super partition metadata slot |slot|. - bool IsSuperBlockDevice(const base::FilePath& device_dir, - Slot slot, - const std::string& partition_name_suffix) const; - - // Whether the target partitions should be loaded as dynamic partitions. Set - // by InitPartitionMetadata() per each update. - bool is_target_dynamic_{false}; - DISALLOW_COPY_AND_ASSIGN(BootControlAndroid); }; diff --git a/boot_control_android_unittest.cc b/boot_control_android_unittest.cc deleted file mode 100644 index bb9903e2..00000000 --- a/boot_control_android_unittest.cc +++ /dev/null @@ -1,853 +0,0 @@ -// -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include "update_engine/boot_control_android.h" - -#include <set> -#include <vector> - -#include <base/logging.h> -#include <base/strings/string_util.h> -#include <fs_mgr.h> -#include <gmock/gmock.h> -#include <gtest/gtest.h> -#include <libdm/dm.h> - -#include "update_engine/mock_boot_control_hal.h" -#include "update_engine/mock_dynamic_partition_control.h" - -using android::dm::DmDeviceState; -using android::fs_mgr::MetadataBuilder; -using android::hardware::Void; -using std::string; -using testing::_; -using testing::AnyNumber; -using testing::Contains; -using testing::Eq; -using testing::Invoke; -using testing::Key; -using testing::MakeMatcher; -using testing::Matcher; -using testing::MatcherInterface; -using testing::MatchResultListener; -using testing::NiceMock; -using testing::Not; -using testing::Return; - -namespace chromeos_update_engine { - -constexpr const uint32_t kMaxNumSlots = 2; -constexpr const char* kSlotSuffixes[kMaxNumSlots] = {"_a", "_b"}; -constexpr const char* kFakeDevicePath = "/fake/dev/path/"; -constexpr const char* kFakeDmDevicePath = "/fake/dm/dev/path/"; -constexpr const uint32_t kFakeMetadataSize = 65536; -constexpr const char* kDefaultGroup = "foo"; - -// A map describing the size of each partition. -// "{name, size}" -using PartitionSizes = std::map<string, uint64_t>; - -// "{name_a, size}" -using PartitionSuffixSizes = std::map<string, uint64_t>; - -using PartitionMetadata = BootControlInterface::PartitionMetadata; - -// C++ standards do not allow uint64_t (aka unsigned long) to be the parameter -// of user-defined literal operators. -constexpr unsigned long long operator"" _MiB(unsigned long long x) { // NOLINT - return x << 20; -} -constexpr unsigned long long operator"" _GiB(unsigned long long x) { // NOLINT - return x << 30; -} - -constexpr uint64_t kDefaultGroupSize = 5_GiB; -// Super device size. 1 MiB for metadata. -constexpr uint64_t kDefaultSuperSize = kDefaultGroupSize * 2 + 1_MiB; - -template <typename U, typename V> -std::ostream& operator<<(std::ostream& os, const std::map<U, V>& param) { - os << "{"; - bool first = true; - for (const auto& pair : param) { - if (!first) - os << ", "; - os << pair.first << ":" << pair.second; - first = false; - } - return os << "}"; -} - -template <typename T> -std::ostream& operator<<(std::ostream& os, const std::vector<T>& param) { - os << "["; - bool first = true; - for (const auto& e : param) { - if (!first) - os << ", "; - os << e; - first = false; - } - return os << "]"; -} - -std::ostream& operator<<(std::ostream& os, - const PartitionMetadata::Partition& p) { - return os << "{" << p.name << ", " << p.size << "}"; -} - -std::ostream& operator<<(std::ostream& os, const PartitionMetadata::Group& g) { - return os << "{" << g.name << ", " << g.size << ", " << g.partitions << "}"; -} - -std::ostream& operator<<(std::ostream& os, const PartitionMetadata& m) { - return os << m.groups; -} - -inline string GetDevice(const string& name) { - return kFakeDevicePath + name; -} - -inline string GetDmDevice(const string& name) { - return kFakeDmDevicePath + name; -} - -// TODO(elsk): fs_mgr_get_super_partition_name should be mocked. -inline string GetSuperDevice(uint32_t slot) { - return GetDevice(fs_mgr_get_super_partition_name(slot)); -} - -struct TestParam { - uint32_t source; - uint32_t target; -}; -std::ostream& operator<<(std::ostream& os, const TestParam& param) { - return os << "{source: " << param.source << ", target:" << param.target - << "}"; -} - -// To support legacy tests, auto-convert {name_a: size} map to -// PartitionMetadata. -PartitionMetadata partitionSuffixSizesToMetadata( - const PartitionSuffixSizes& partition_sizes) { - PartitionMetadata metadata; - for (const char* suffix : kSlotSuffixes) { - metadata.groups.push_back( - {string(kDefaultGroup) + suffix, kDefaultGroupSize, {}}); - } - for (const auto& pair : partition_sizes) { - for (size_t suffix_idx = 0; suffix_idx < kMaxNumSlots; ++suffix_idx) { - if (base::EndsWith(pair.first, - kSlotSuffixes[suffix_idx], - base::CompareCase::SENSITIVE)) { - metadata.groups[suffix_idx].partitions.push_back( - {pair.first, pair.second}); - } - } - } - return metadata; -} - -// To support legacy tests, auto-convert {name: size} map to PartitionMetadata. -PartitionMetadata partitionSizesToMetadata( - const PartitionSizes& partition_sizes) { - PartitionMetadata metadata; - metadata.groups.push_back({string{kDefaultGroup}, kDefaultGroupSize, {}}); - for (const auto& pair : partition_sizes) { - metadata.groups[0].partitions.push_back({pair.first, pair.second}); - } - return metadata; -} - -std::unique_ptr<MetadataBuilder> NewFakeMetadata( - const PartitionMetadata& metadata) { - auto builder = - MetadataBuilder::New(kDefaultSuperSize, kFakeMetadataSize, kMaxNumSlots); - EXPECT_GE(builder->AllocatableSpace(), kDefaultGroupSize * 2); - EXPECT_NE(nullptr, builder); - if (builder == nullptr) - return nullptr; - for (const auto& group : metadata.groups) { - EXPECT_TRUE(builder->AddGroup(group.name, group.size)); - for (const auto& partition : group.partitions) { - auto p = builder->AddPartition(partition.name, group.name, 0 /* attr */); - EXPECT_TRUE(p && builder->ResizePartition(p, partition.size)); - } - } - return builder; -} - -class MetadataMatcher : public MatcherInterface<MetadataBuilder*> { - public: - explicit MetadataMatcher(const PartitionSuffixSizes& partition_sizes) - : partition_metadata_(partitionSuffixSizesToMetadata(partition_sizes)) {} - explicit MetadataMatcher(const PartitionMetadata& partition_metadata) - : partition_metadata_(partition_metadata) {} - - bool MatchAndExplain(MetadataBuilder* metadata, - MatchResultListener* listener) const override { - bool success = true; - for (const auto& group : partition_metadata_.groups) { - for (const auto& partition : group.partitions) { - auto p = metadata->FindPartition(partition.name); - if (p == nullptr) { - if (!success) - *listener << "; "; - *listener << "No partition " << partition.name; - success = false; - continue; - } - if (p->size() != partition.size) { - if (!success) - *listener << "; "; - *listener << "Partition " << partition.name << " has size " - << p->size() << ", expected " << partition.size; - success = false; - } - if (p->group_name() != group.name) { - if (!success) - *listener << "; "; - *listener << "Partition " << partition.name << " has group " - << p->group_name() << ", expected " << group.name; - success = false; - } - } - } - return success; - } - - void DescribeTo(std::ostream* os) const override { - *os << "expect: " << partition_metadata_; - } - - void DescribeNegationTo(std::ostream* os) const override { - *os << "expect not: " << partition_metadata_; - } - - private: - PartitionMetadata partition_metadata_; -}; - -inline Matcher<MetadataBuilder*> MetadataMatches( - const PartitionSuffixSizes& partition_sizes) { - return MakeMatcher(new MetadataMatcher(partition_sizes)); -} - -inline Matcher<MetadataBuilder*> MetadataMatches( - const PartitionMetadata& partition_metadata) { - return MakeMatcher(new MetadataMatcher(partition_metadata)); -} - -MATCHER_P(HasGroup, group, " has group " + group) { - auto groups = arg->ListGroups(); - return std::find(groups.begin(), groups.end(), group) != groups.end(); -} - -class BootControlAndroidTest : public ::testing::Test { - protected: - void SetUp() override { - // Fake init bootctl_ - bootctl_.module_ = new NiceMock<MockBootControlHal>(); - bootctl_.dynamic_control_ = - std::make_unique<NiceMock<MockDynamicPartitionControl>>(); - - ON_CALL(module(), getNumberSlots()).WillByDefault(Invoke([] { - return kMaxNumSlots; - })); - ON_CALL(module(), getSuffix(_, _)) - .WillByDefault(Invoke([](auto slot, auto cb) { - EXPECT_LE(slot, kMaxNumSlots); - cb(slot < kMaxNumSlots ? kSlotSuffixes[slot] : ""); - return Void(); - })); - - ON_CALL(dynamicControl(), IsDynamicPartitionsEnabled()) - .WillByDefault(Return(true)); - ON_CALL(dynamicControl(), IsDynamicPartitionsRetrofit()) - .WillByDefault(Return(false)); - ON_CALL(dynamicControl(), DeviceExists(_)).WillByDefault(Return(true)); - ON_CALL(dynamicControl(), GetDeviceDir(_)) - .WillByDefault(Invoke([](auto path) { - *path = kFakeDevicePath; - return true; - })); - ON_CALL(dynamicControl(), GetDmDevicePathByName(_, _)) - .WillByDefault(Invoke([](auto partition_name_suffix, auto device) { - *device = GetDmDevice(partition_name_suffix); - return true; - })); - } - - // Return the mocked HAL module. - NiceMock<MockBootControlHal>& module() { - return static_cast<NiceMock<MockBootControlHal>&>(*bootctl_.module_); - } - - // Return the mocked DynamicPartitionControlInterface. - NiceMock<MockDynamicPartitionControl>& dynamicControl() { - return static_cast<NiceMock<MockDynamicPartitionControl>&>( - *bootctl_.dynamic_control_); - } - - // Set the fake metadata to return when LoadMetadataBuilder is called on - // |slot|. - void SetMetadata(uint32_t slot, const PartitionSuffixSizes& sizes) { - SetMetadata(slot, partitionSuffixSizesToMetadata(sizes)); - } - - void SetMetadata(uint32_t slot, const PartitionMetadata& metadata) { - EXPECT_CALL(dynamicControl(), - LoadMetadataBuilder(GetSuperDevice(slot), slot, _)) - .Times(AnyNumber()) - .WillRepeatedly(Invoke([metadata](auto, auto, auto) { - return NewFakeMetadata(metadata); - })); - } - - // Expect that UnmapPartitionOnDeviceMapper is called on target() metadata - // slot with each partition in |partitions|. - void ExpectUnmap(const std::set<string>& partitions) { - // Error when UnmapPartitionOnDeviceMapper is called on unknown arguments. - ON_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(_, _)) - .WillByDefault(Return(false)); - - for (const auto& partition : partitions) { - EXPECT_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(partition, _)) - .WillOnce(Invoke([this](auto partition, auto) { - mapped_devices_.erase(partition); - return true; - })); - } - } - - void ExpectDevicesAreMapped(const std::set<string>& partitions) { - ASSERT_EQ(partitions.size(), mapped_devices_.size()); - for (const auto& partition : partitions) { - EXPECT_THAT(mapped_devices_, Contains(Key(Eq(partition)))) - << "Expect that " << partition << " is mapped, but it is not."; - } - } - - void ExpectStoreMetadata(const PartitionSuffixSizes& partition_sizes) { - ExpectStoreMetadataMatch(MetadataMatches(partition_sizes)); - } - - virtual void ExpectStoreMetadataMatch( - const Matcher<MetadataBuilder*>& matcher) { - EXPECT_CALL(dynamicControl(), - StoreMetadata(GetSuperDevice(target()), matcher, target())) - .WillOnce(Return(true)); - } - - uint32_t source() { return slots_.source; } - - uint32_t target() { return slots_.target; } - - // Return partition names with suffix of source(). - string S(const string& name) { return name + kSlotSuffixes[source()]; } - - // Return partition names with suffix of target(). - string T(const string& name) { return name + kSlotSuffixes[target()]; } - - // Set source and target slots to use before testing. - void SetSlots(const TestParam& slots) { - slots_ = slots; - - ON_CALL(module(), getCurrentSlot()).WillByDefault(Invoke([this] { - return source(); - })); - // Should not store metadata to source slot. - EXPECT_CALL(dynamicControl(), - StoreMetadata(GetSuperDevice(source()), _, source())) - .Times(0); - // Should not load metadata from target slot. - EXPECT_CALL(dynamicControl(), - LoadMetadataBuilder(GetSuperDevice(target()), target(), _)) - .Times(0); - } - - bool InitPartitionMetadata(uint32_t slot, - PartitionSizes partition_sizes, - bool update_metadata = true) { - auto m = partitionSizesToMetadata(partition_sizes); - LOG(INFO) << m; - return bootctl_.InitPartitionMetadata(slot, m, update_metadata); - } - - BootControlAndroid bootctl_; // BootControlAndroid under test. - TestParam slots_; - // mapped devices through MapPartitionOnDeviceMapper. - std::map<string, string> mapped_devices_; -}; - -class BootControlAndroidTestP - : public BootControlAndroidTest, - public ::testing::WithParamInterface<TestParam> { - public: - void SetUp() override { - BootControlAndroidTest::SetUp(); - SetSlots(GetParam()); - } -}; - -// Test resize case. Grow if target metadata contains a partition with a size -// less than expected. -TEST_P(BootControlAndroidTestP, NeedGrowIfSizeNotMatchWhenResizing) { - SetMetadata(source(), - {{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 1_GiB}}); - ExpectStoreMetadata({{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 3_GiB}, - {T("vendor"), 1_GiB}}); - ExpectUnmap({T("system"), T("vendor")}); - - EXPECT_TRUE( - InitPartitionMetadata(target(), {{"system", 3_GiB}, {"vendor", 1_GiB}})); -} - -// Test resize case. Shrink if target metadata contains a partition with a size -// greater than expected. -TEST_P(BootControlAndroidTestP, NeedShrinkIfSizeNotMatchWhenResizing) { - SetMetadata(source(), - {{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 1_GiB}}); - ExpectStoreMetadata({{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 150_MiB}}); - ExpectUnmap({T("system"), T("vendor")}); - - EXPECT_TRUE(InitPartitionMetadata(target(), - {{"system", 2_GiB}, {"vendor", 150_MiB}})); -} - -// Test adding partitions on the first run. -TEST_P(BootControlAndroidTestP, AddPartitionToEmptyMetadata) { - SetMetadata(source(), PartitionSuffixSizes{}); - ExpectStoreMetadata({{T("system"), 2_GiB}, {T("vendor"), 1_GiB}}); - ExpectUnmap({T("system"), T("vendor")}); - - EXPECT_TRUE( - InitPartitionMetadata(target(), {{"system", 2_GiB}, {"vendor", 1_GiB}})); -} - -// Test subsequent add case. -TEST_P(BootControlAndroidTestP, AddAdditionalPartition) { - SetMetadata(source(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}}); - ExpectStoreMetadata( - {{S("system"), 2_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}}); - ExpectUnmap({T("system"), T("vendor")}); - - EXPECT_TRUE( - InitPartitionMetadata(target(), {{"system", 2_GiB}, {"vendor", 1_GiB}})); -} - -// Test delete one partition. -TEST_P(BootControlAndroidTestP, DeletePartition) { - SetMetadata(source(), - {{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 1_GiB}}); - // No T("vendor") - ExpectStoreMetadata( - {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 2_GiB}}); - ExpectUnmap({T("system")}); - - EXPECT_TRUE(InitPartitionMetadata(target(), {{"system", 2_GiB}})); -} - -// Test delete all partitions. -TEST_P(BootControlAndroidTestP, DeleteAll) { - SetMetadata(source(), - {{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 1_GiB}}); - ExpectStoreMetadata({{S("system"), 2_GiB}, {S("vendor"), 1_GiB}}); - - EXPECT_TRUE(InitPartitionMetadata(target(), {})); -} - -// Test corrupt source metadata case. -TEST_P(BootControlAndroidTestP, CorruptedSourceMetadata) { - EXPECT_CALL(dynamicControl(), - LoadMetadataBuilder(GetSuperDevice(source()), source(), _)) - .WillOnce(Invoke([](auto, auto, auto) { return nullptr; })); - ExpectUnmap({T("system")}); - - EXPECT_FALSE(InitPartitionMetadata(target(), {{"system", 1_GiB}})) - << "Should not be able to continue with corrupt source metadata"; -} - -// Test that InitPartitionMetadata fail if there is not enough space on the -// device. -TEST_P(BootControlAndroidTestP, NotEnoughSpace) { - SetMetadata(source(), - {{S("system"), 3_GiB}, - {S("vendor"), 2_GiB}, - {T("system"), 0}, - {T("vendor"), 0}}); - EXPECT_FALSE( - InitPartitionMetadata(target(), {{"system", 3_GiB}, {"vendor", 3_GiB}})) - << "Should not be able to fit 11GiB data into 10GiB space"; -} - -TEST_P(BootControlAndroidTestP, NotEnoughSpaceForSlot) { - SetMetadata(source(), - {{S("system"), 1_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 0}, - {T("vendor"), 0}}); - EXPECT_FALSE( - InitPartitionMetadata(target(), {{"system", 3_GiB}, {"vendor", 3_GiB}})) - << "Should not be able to grow over size of super / 2"; -} - -// Test applying retrofit update on a build with dynamic partitions enabled. -TEST_P(BootControlAndroidTestP, - ApplyRetrofitUpdateOnDynamicPartitionsEnabledBuild) { - SetMetadata(source(), - {{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 1_GiB}}); - // Should not try to unmap any target partition. - EXPECT_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(_, _)).Times(0); - // Should not store metadata to target slot. - EXPECT_CALL(dynamicControl(), - StoreMetadata(GetSuperDevice(target()), _, target())) - .Times(0); - - // Not calling through BootControlAndroidTest::InitPartitionMetadata(), since - // we don't want any default group in the PartitionMetadata. - EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(), {}, true)); - - // Should use dynamic source partitions. - EXPECT_CALL(dynamicControl(), GetState(S("system"))) - .Times(1) - .WillOnce(Return(DmDeviceState::ACTIVE)); - string system_device; - EXPECT_TRUE(bootctl_.GetPartitionDevice("system", source(), &system_device)); - EXPECT_EQ(GetDmDevice(S("system")), system_device); - - // Should use static target partitions without querying dynamic control. - EXPECT_CALL(dynamicControl(), GetState(T("system"))).Times(0); - EXPECT_TRUE(bootctl_.GetPartitionDevice("system", target(), &system_device)); - EXPECT_EQ(GetDevice(T("system")), system_device); - - // Static partition "bar". - EXPECT_CALL(dynamicControl(), GetState(S("bar"))).Times(0); - std::string bar_device; - EXPECT_TRUE(bootctl_.GetPartitionDevice("bar", source(), &bar_device)); - EXPECT_EQ(GetDevice(S("bar")), bar_device); - - EXPECT_CALL(dynamicControl(), GetState(T("bar"))).Times(0); - EXPECT_TRUE(bootctl_.GetPartitionDevice("bar", target(), &bar_device)); - EXPECT_EQ(GetDevice(T("bar")), bar_device); -} - -TEST_P(BootControlAndroidTestP, GetPartitionDeviceWhenResumingUpdate) { - // Both of the two slots contain valid partition metadata, since this is - // resuming an update. - SetMetadata(source(), - {{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 1_GiB}}); - SetMetadata(target(), - {{S("system"), 2_GiB}, - {S("vendor"), 1_GiB}, - {T("system"), 2_GiB}, - {T("vendor"), 1_GiB}}); - EXPECT_CALL(dynamicControl(), - StoreMetadata(GetSuperDevice(target()), _, target())) - .Times(0); - EXPECT_TRUE(InitPartitionMetadata( - target(), {{"system", 2_GiB}, {"vendor", 1_GiB}}, false)); - - // Dynamic partition "system". - EXPECT_CALL(dynamicControl(), GetState(S("system"))) - .Times(1) - .WillOnce(Return(DmDeviceState::ACTIVE)); - string system_device; - EXPECT_TRUE(bootctl_.GetPartitionDevice("system", source(), &system_device)); - EXPECT_EQ(GetDmDevice(S("system")), system_device); - - EXPECT_CALL(dynamicControl(), GetState(T("system"))) - .Times(1) - .WillOnce(Return(DmDeviceState::ACTIVE)); - EXPECT_TRUE(bootctl_.GetPartitionDevice("system", target(), &system_device)); - EXPECT_EQ(GetDmDevice(T("system")), system_device); - - // Static partition "bar". - EXPECT_CALL(dynamicControl(), GetState(S("bar"))).Times(0); - std::string bar_device; - EXPECT_TRUE(bootctl_.GetPartitionDevice("bar", source(), &bar_device)); - EXPECT_EQ(GetDevice(S("bar")), bar_device); - - EXPECT_CALL(dynamicControl(), GetState(T("bar"))).Times(0); - EXPECT_TRUE(bootctl_.GetPartitionDevice("bar", target(), &bar_device)); - EXPECT_EQ(GetDevice(T("bar")), bar_device); -} - -INSTANTIATE_TEST_CASE_P(BootControlAndroidTest, - BootControlAndroidTestP, - testing::Values(TestParam{0, 1}, TestParam{1, 0})); - -const PartitionSuffixSizes update_sizes_0() { - // Initial state is 0 for "other" slot. - return { - {"grown_a", 2_GiB}, - {"shrunk_a", 1_GiB}, - {"same_a", 100_MiB}, - {"deleted_a", 150_MiB}, - // no added_a - {"grown_b", 200_MiB}, - // simulate system_other - {"shrunk_b", 0}, - {"same_b", 0}, - {"deleted_b", 0}, - // no added_b - }; -} - -const PartitionSuffixSizes update_sizes_1() { - return { - {"grown_a", 2_GiB}, - {"shrunk_a", 1_GiB}, - {"same_a", 100_MiB}, - {"deleted_a", 150_MiB}, - // no added_a - {"grown_b", 3_GiB}, - {"shrunk_b", 150_MiB}, - {"same_b", 100_MiB}, - {"added_b", 150_MiB}, - // no deleted_b - }; -} - -const PartitionSuffixSizes update_sizes_2() { - return { - {"grown_a", 4_GiB}, - {"shrunk_a", 100_MiB}, - {"same_a", 100_MiB}, - {"deleted_a", 64_MiB}, - // no added_a - {"grown_b", 3_GiB}, - {"shrunk_b", 150_MiB}, - {"same_b", 100_MiB}, - {"added_b", 150_MiB}, - // no deleted_b - }; -} - -// Test case for first update after the device is manufactured, in which -// case the "other" slot is likely of size "0" (except system, which is -// non-zero because of system_other partition) -TEST_F(BootControlAndroidTest, SimulatedFirstUpdate) { - SetSlots({0, 1}); - - SetMetadata(source(), update_sizes_0()); - SetMetadata(target(), update_sizes_0()); - ExpectStoreMetadata(update_sizes_1()); - ExpectUnmap({"grown_b", "shrunk_b", "same_b", "added_b"}); - - EXPECT_TRUE(InitPartitionMetadata(target(), - {{"grown", 3_GiB}, - {"shrunk", 150_MiB}, - {"same", 100_MiB}, - {"added", 150_MiB}})); -} - -// After first update, test for the second update. In the second update, the -// "added" partition is deleted and "deleted" partition is re-added. -TEST_F(BootControlAndroidTest, SimulatedSecondUpdate) { - SetSlots({1, 0}); - - SetMetadata(source(), update_sizes_1()); - SetMetadata(target(), update_sizes_0()); - - ExpectStoreMetadata(update_sizes_2()); - ExpectUnmap({"grown_a", "shrunk_a", "same_a", "deleted_a"}); - - EXPECT_TRUE(InitPartitionMetadata(target(), - {{"grown", 4_GiB}, - {"shrunk", 100_MiB}, - {"same", 100_MiB}, - {"deleted", 64_MiB}})); -} - -TEST_F(BootControlAndroidTest, ApplyingToCurrentSlot) { - SetSlots({1, 1}); - EXPECT_FALSE(InitPartitionMetadata(target(), {})) - << "Should not be able to apply to current slot."; -} - -class BootControlAndroidGroupTestP : public BootControlAndroidTestP { - public: - void SetUp() override { - BootControlAndroidTestP::SetUp(); - SetMetadata( - source(), - {.groups = {SimpleGroup(S("android"), 3_GiB, S("system"), 2_GiB), - SimpleGroup(S("oem"), 2_GiB, S("vendor"), 1_GiB), - SimpleGroup(T("android"), 3_GiB, T("system"), 0), - SimpleGroup(T("oem"), 2_GiB, T("vendor"), 0)}}); - } - - // Return a simple group with only one partition. - PartitionMetadata::Group SimpleGroup(const string& group, - uint64_t group_size, - const string& partition, - uint64_t partition_size) { - return {.name = group, - .size = group_size, - .partitions = {{.name = partition, .size = partition_size}}}; - } - - void ExpectStoreMetadata(const PartitionMetadata& partition_metadata) { - ExpectStoreMetadataMatch(MetadataMatches(partition_metadata)); - } - - // Expect that target slot is stored with target groups. - void ExpectStoreMetadataMatch( - const Matcher<MetadataBuilder*>& matcher) override { - BootControlAndroidTestP::ExpectStoreMetadataMatch(AllOf( - MetadataMatches(PartitionMetadata{ - .groups = {SimpleGroup(S("android"), 3_GiB, S("system"), 2_GiB), - SimpleGroup(S("oem"), 2_GiB, S("vendor"), 1_GiB)}}), - matcher)); - } -}; - -// Allow to resize within group. -TEST_P(BootControlAndroidGroupTestP, ResizeWithinGroup) { - ExpectStoreMetadata(PartitionMetadata{ - .groups = {SimpleGroup(T("android"), 3_GiB, T("system"), 3_GiB), - SimpleGroup(T("oem"), 2_GiB, T("vendor"), 2_GiB)}}); - ExpectUnmap({T("system"), T("vendor")}); - - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), - PartitionMetadata{ - .groups = {SimpleGroup("android", 3_GiB, "system", 3_GiB), - SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}}, - true)); -} - -TEST_P(BootControlAndroidGroupTestP, NotEnoughSpaceForGroup) { - EXPECT_FALSE(bootctl_.InitPartitionMetadata( - target(), - PartitionMetadata{ - .groups = {SimpleGroup("android", 3_GiB, "system", 1_GiB), - SimpleGroup("oem", 2_GiB, "vendor", 3_GiB)}}, - true)) - << "Should not be able to grow over maximum size of group"; -} - -TEST_P(BootControlAndroidGroupTestP, GroupTooBig) { - EXPECT_FALSE(bootctl_.InitPartitionMetadata( - target(), - PartitionMetadata{.groups = {{.name = "android", .size = 3_GiB}, - {.name = "oem", .size = 3_GiB}}}, - true)) - << "Should not be able to grow over size of super / 2"; -} - -TEST_P(BootControlAndroidGroupTestP, AddPartitionToGroup) { - ExpectStoreMetadata(PartitionMetadata{ - .groups = { - {.name = T("android"), - .size = 3_GiB, - .partitions = {{.name = T("system"), .size = 2_GiB}, - {.name = T("product_services"), .size = 1_GiB}}}}}); - ExpectUnmap({T("system"), T("vendor"), T("product_services")}); - - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), - PartitionMetadata{ - .groups = {{.name = "android", - .size = 3_GiB, - .partitions = {{.name = "system", .size = 2_GiB}, - {.name = "product_services", - .size = 1_GiB}}}, - SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}}, - true)); -} - -TEST_P(BootControlAndroidGroupTestP, RemovePartitionFromGroup) { - ExpectStoreMetadata(PartitionMetadata{ - .groups = {{.name = T("android"), .size = 3_GiB, .partitions = {}}}}); - ExpectUnmap({T("vendor")}); - - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), - PartitionMetadata{ - .groups = {{.name = "android", .size = 3_GiB, .partitions = {}}, - SimpleGroup("oem", 2_GiB, "vendor", 2_GiB)}}, - true)); -} - -TEST_P(BootControlAndroidGroupTestP, AddGroup) { - ExpectStoreMetadata(PartitionMetadata{ - .groups = { - SimpleGroup(T("new_group"), 2_GiB, T("new_partition"), 2_GiB)}}); - ExpectUnmap({T("system"), T("vendor"), T("new_partition")}); - - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), - PartitionMetadata{ - .groups = {SimpleGroup("android", 2_GiB, "system", 2_GiB), - SimpleGroup("oem", 1_GiB, "vendor", 1_GiB), - SimpleGroup("new_group", 2_GiB, "new_partition", 2_GiB)}}, - true)); -} - -TEST_P(BootControlAndroidGroupTestP, RemoveGroup) { - ExpectStoreMetadataMatch(Not(HasGroup(T("oem")))); - ExpectUnmap({T("system")}); - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), - PartitionMetadata{ - .groups = {SimpleGroup("android", 2_GiB, "system", 2_GiB)}}, - true)); -} - -TEST_P(BootControlAndroidGroupTestP, ResizeGroup) { - ExpectStoreMetadata(PartitionMetadata{ - .groups = {SimpleGroup(T("android"), 2_GiB, T("system"), 2_GiB), - SimpleGroup(T("oem"), 3_GiB, T("vendor"), 3_GiB)}}); - ExpectUnmap({T("system"), T("vendor")}); - - EXPECT_TRUE(bootctl_.InitPartitionMetadata( - target(), - PartitionMetadata{ - .groups = {SimpleGroup("android", 2_GiB, "system", 2_GiB), - SimpleGroup("oem", 3_GiB, "vendor", 3_GiB)}}, - true)); -} - -INSTANTIATE_TEST_CASE_P(BootControlAndroidTest, - BootControlAndroidGroupTestP, - testing::Values(TestParam{0, 1}, TestParam{1, 0})); - -} // namespace chromeos_update_engine diff --git a/boot_control_chromeos.cc b/boot_control_chromeos.cc index 3f1eac44..95456f00 100644 --- a/boot_control_chromeos.cc +++ b/boot_control_chromeos.cc @@ -34,6 +34,7 @@ extern "C" { } #include "update_engine/common/boot_control.h" +#include "update_engine/common/dynamic_partition_control_stub.h" #include "update_engine/common/subprocess.h" #include "update_engine/common/utils.h" @@ -134,6 +135,8 @@ bool BootControlChromeOS::Init() { return false; } + dynamic_partition_control_.reset(new DynamicPartitionControlStub()); + LOG(INFO) << "Booted from slot " << current_slot_ << " (slot " << SlotName(current_slot_) << ") of " << num_slots_ << " slots present on disk " << boot_disk_name_; @@ -173,9 +176,11 @@ bool BootControlChromeOS::ParseDlcPartitionName( return true; } -bool BootControlChromeOS::GetPartitionDevice(const string& partition_name, - unsigned int slot, - string* device) const { +bool BootControlChromeOS::GetPartitionDevice(const std::string& partition_name, + BootControlInterface::Slot slot, + bool not_in_payload, + std::string* device, + bool* is_dynamic) const { // Partition name prefixed with |kPartitionNamePrefixDlc| is a DLC module. if (base::StartsWith(partition_name, kPartitionNamePrefixDlc, @@ -201,9 +206,18 @@ bool BootControlChromeOS::GetPartitionDevice(const string& partition_name, return false; *device = part_device; + if (is_dynamic) { + *is_dynamic = false; + } return true; } +bool BootControlChromeOS::GetPartitionDevice(const string& partition_name, + BootControlInterface::Slot slot, + string* device) const { + return GetPartitionDevice(partition_name, slot, false, device, nullptr); +} + bool BootControlChromeOS::IsSlotBootable(Slot slot) const { int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot); if (partition_num < 0) @@ -350,13 +364,14 @@ int BootControlChromeOS::GetPartitionNumber( return -1; } -bool BootControlChromeOS::InitPartitionMetadata( - Slot slot, - const PartitionMetadata& partition_metadata, - bool update_metadata) { - return true; +bool BootControlChromeOS::IsSlotMarkedSuccessful(Slot slot) const { + LOG(ERROR) << __func__ << " not supported."; + return false; } -void BootControlChromeOS::Cleanup() {} +DynamicPartitionControlInterface* +BootControlChromeOS::GetDynamicPartitionControl() { + return dynamic_partition_control_.get(); +} } // namespace chromeos_update_engine diff --git a/boot_control_chromeos.h b/boot_control_chromeos.h index 109197fe..42716720 100644 --- a/boot_control_chromeos.h +++ b/boot_control_chromeos.h @@ -17,12 +17,14 @@ #ifndef UPDATE_ENGINE_BOOT_CONTROL_CHROMEOS_H_ #define UPDATE_ENGINE_BOOT_CONTROL_CHROMEOS_H_ +#include <memory> #include <string> #include <base/callback.h> #include <gtest/gtest_prod.h> // for FRIEND_TEST #include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/dynamic_partition_control_interface.h" namespace chromeos_update_engine { @@ -45,15 +47,18 @@ class BootControlChromeOS : public BootControlInterface { BootControlInterface::Slot GetCurrentSlot() const override; bool GetPartitionDevice(const std::string& partition_name, BootControlInterface::Slot slot, + bool not_in_payload, + std::string* device, + bool* is_dynamic) const override; + bool GetPartitionDevice(const std::string& partition_name, + BootControlInterface::Slot slot, std::string* device) const override; bool IsSlotBootable(BootControlInterface::Slot slot) const override; bool MarkSlotUnbootable(BootControlInterface::Slot slot) override; bool SetActiveBootSlot(BootControlInterface::Slot slot) override; bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override; - bool InitPartitionMetadata(Slot slot, - const PartitionMetadata& partition_metadata, - bool update_metadata) override; - void Cleanup() override; + bool IsSlotMarkedSuccessful(BootControlInterface::Slot slot) const override; + DynamicPartitionControlInterface* GetDynamicPartitionControl() override; private: friend class BootControlChromeOSTest; @@ -77,7 +82,7 @@ class BootControlChromeOS : public BootControlInterface { // Extracts DLC module ID and package ID from partition name. The structure of // the partition name is dlc/<dlc-id>/<dlc-package>. For example: - // dlc/dummy-dlc/dummy-package + // dlc/fake-dlc/fake-package bool ParseDlcPartitionName(const std::string partition_name, std::string* dlc_id, std::string* dlc_package) const; @@ -89,6 +94,8 @@ class BootControlChromeOS : public BootControlInterface { // The block device of the disk we booted from, without the partition number. std::string boot_disk_name_; + std::unique_ptr<DynamicPartitionControlInterface> dynamic_partition_control_; + DISALLOW_COPY_AND_ASSIGN(BootControlChromeOS); }; diff --git a/cleanup_previous_update_action.cc b/cleanup_previous_update_action.cc new file mode 100644 index 00000000..1a2476f0 --- /dev/null +++ b/cleanup_previous_update_action.cc @@ -0,0 +1,425 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "update_engine/cleanup_previous_update_action.h" + +#include <chrono> // NOLINT(build/c++11) -- for merge times +#include <functional> +#include <string> +#include <type_traits> + +#include <android-base/properties.h> +#include <base/bind.h> + +#ifndef __ANDROID_RECOVERY__ +#include <statslog.h> +#endif + +#include "update_engine/common/utils.h" +#include "update_engine/payload_consumer/delta_performer.h" + +using android::base::GetBoolProperty; +using android::snapshot::ISnapshotManager; +using android::snapshot::SnapshotMergeStats; +using android::snapshot::UpdateState; +using brillo::MessageLoop; + +constexpr char kBootCompletedProp[] = "sys.boot_completed"; +// Interval to check sys.boot_completed. +constexpr auto kCheckBootCompletedInterval = base::TimeDelta::FromSeconds(2); +// Interval to check IBootControl::isSlotMarkedSuccessful +constexpr auto kCheckSlotMarkedSuccessfulInterval = + base::TimeDelta::FromSeconds(2); +// Interval to call SnapshotManager::ProcessUpdateState +constexpr auto kWaitForMergeInterval = base::TimeDelta::FromSeconds(2); + +#ifdef __ANDROID_RECOVERY__ +static constexpr bool kIsRecovery = true; +#else +static constexpr bool kIsRecovery = false; +#endif + +namespace chromeos_update_engine { + +CleanupPreviousUpdateAction::CleanupPreviousUpdateAction( + PrefsInterface* prefs, + BootControlInterface* boot_control, + android::snapshot::ISnapshotManager* snapshot, + CleanupPreviousUpdateActionDelegateInterface* delegate) + : prefs_(prefs), + boot_control_(boot_control), + snapshot_(snapshot), + delegate_(delegate), + running_(false), + cancel_failed_(false), + last_percentage_(0), + merge_stats_(nullptr) {} + +void CleanupPreviousUpdateAction::PerformAction() { + ResumeAction(); +} + +void CleanupPreviousUpdateAction::TerminateProcessing() { + SuspendAction(); +} + +void CleanupPreviousUpdateAction::ResumeAction() { + CHECK(prefs_); + CHECK(boot_control_); + + LOG(INFO) << "Starting/resuming CleanupPreviousUpdateAction"; + running_ = true; + StartActionInternal(); +} + +void CleanupPreviousUpdateAction::SuspendAction() { + LOG(INFO) << "Stopping/suspending CleanupPreviousUpdateAction"; + running_ = false; +} + +void CleanupPreviousUpdateAction::ActionCompleted(ErrorCode error_code) { + running_ = false; + ReportMergeStats(); + metadata_device_ = nullptr; +} + +std::string CleanupPreviousUpdateAction::Type() const { + return StaticType(); +} + +std::string CleanupPreviousUpdateAction::StaticType() { + return "CleanupPreviousUpdateAction"; +} + +void CleanupPreviousUpdateAction::StartActionInternal() { + // Do nothing on non-VAB device. + if (!boot_control_->GetDynamicPartitionControl() + ->GetVirtualAbFeatureFlag() + .IsEnabled()) { + processor_->ActionComplete(this, ErrorCode::kSuccess); + return; + } + // SnapshotManager must be available on VAB devices. + CHECK(snapshot_ != nullptr); + merge_stats_ = snapshot_->GetSnapshotMergeStatsInstance(); + CHECK(merge_stats_ != nullptr); + WaitBootCompletedOrSchedule(); +} + +void CleanupPreviousUpdateAction::ScheduleWaitBootCompleted() { + TEST_AND_RETURN(running_); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&CleanupPreviousUpdateAction::WaitBootCompletedOrSchedule, + base::Unretained(this)), + kCheckBootCompletedInterval); +} + +void CleanupPreviousUpdateAction::WaitBootCompletedOrSchedule() { + TEST_AND_RETURN(running_); + if (!kIsRecovery && + !android::base::GetBoolProperty(kBootCompletedProp, false)) { + // repeat + ScheduleWaitBootCompleted(); + return; + } + + LOG(INFO) << "Boot completed, waiting on markBootSuccessful()"; + CheckSlotMarkedSuccessfulOrSchedule(); +} + +void CleanupPreviousUpdateAction::ScheduleWaitMarkBootSuccessful() { + TEST_AND_RETURN(running_); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind( + &CleanupPreviousUpdateAction::CheckSlotMarkedSuccessfulOrSchedule, + base::Unretained(this)), + kCheckSlotMarkedSuccessfulInterval); +} + +void CleanupPreviousUpdateAction::CheckSlotMarkedSuccessfulOrSchedule() { + TEST_AND_RETURN(running_); + if (!kIsRecovery && + !boot_control_->IsSlotMarkedSuccessful(boot_control_->GetCurrentSlot())) { + ScheduleWaitMarkBootSuccessful(); + } + + if (metadata_device_ == nullptr) { + metadata_device_ = snapshot_->EnsureMetadataMounted(); + } + + if (metadata_device_ == nullptr) { + LOG(ERROR) << "Failed to mount /metadata."; + // If metadata is erased but not formatted, it is possible to not mount + // it in recovery. It is safe to skip CleanupPreviousUpdateAction. + processor_->ActionComplete( + this, kIsRecovery ? ErrorCode::kSuccess : ErrorCode::kError); + return; + } + + if (kIsRecovery) { + auto snapshots_created = + snapshot_->RecoveryCreateSnapshotDevices(metadata_device_); + switch (snapshots_created) { + case android::snapshot::CreateResult::CREATED: { + // If previous update has not finished merging, snapshots exists and are + // created here so that ProcessUpdateState can proceed. + LOG(INFO) << "Snapshot devices are created"; + break; + } + case android::snapshot::CreateResult::NOT_CREATED: { + // If there is no previous update, no snapshot devices are created and + // ProcessUpdateState will return immediately. Hence, NOT_CREATED is not + // considered an error. + LOG(INFO) << "Snapshot devices are not created"; + break; + } + case android::snapshot::CreateResult::ERROR: + default: { + LOG(ERROR) + << "Failed to create snapshot devices (CreateResult = " + << static_cast< + std::underlying_type_t<android::snapshot::CreateResult>>( + snapshots_created); + processor_->ActionComplete(this, ErrorCode::kError); + return; + } + } + } + + if (!merge_stats_->Start()) { + // Not an error because CleanupPreviousUpdateAction may be paused and + // resumed while kernel continues merging snapshots in the background. + LOG(WARNING) << "SnapshotMergeStats::Start failed."; + } + LOG(INFO) << "Waiting for any previous merge request to complete. " + << "This can take up to several minutes."; + WaitForMergeOrSchedule(); +} + +void CleanupPreviousUpdateAction::ScheduleWaitForMerge() { + TEST_AND_RETURN(running_); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&CleanupPreviousUpdateAction::WaitForMergeOrSchedule, + base::Unretained(this)), + kWaitForMergeInterval); +} + +void CleanupPreviousUpdateAction::WaitForMergeOrSchedule() { + TEST_AND_RETURN(running_); + auto state = snapshot_->ProcessUpdateState( + std::bind(&CleanupPreviousUpdateAction::OnMergePercentageUpdate, this), + std::bind(&CleanupPreviousUpdateAction::BeforeCancel, this)); + merge_stats_->set_state(state); + + switch (state) { + case UpdateState::None: { + LOG(INFO) << "Can't find any snapshot to merge."; + ErrorCode error_code = ErrorCode::kSuccess; + if (!snapshot_->CancelUpdate()) { + error_code = ErrorCode::kError; + LOG(INFO) << "Failed to call SnapshotManager::CancelUpdate()."; + } + processor_->ActionComplete(this, error_code); + return; + } + + case UpdateState::Initiated: { + LOG(ERROR) << "Previous update has not been completed, not cleaning up"; + processor_->ActionComplete(this, ErrorCode::kSuccess); + return; + } + + case UpdateState::Unverified: { + InitiateMergeAndWait(); + return; + } + + case UpdateState::Merging: { + ScheduleWaitForMerge(); + return; + } + + case UpdateState::MergeNeedsReboot: { + LOG(ERROR) << "Need reboot to finish merging."; + processor_->ActionComplete(this, ErrorCode::kError); + return; + } + + case UpdateState::MergeCompleted: { + LOG(INFO) << "Merge finished with state MergeCompleted."; + processor_->ActionComplete(this, ErrorCode::kSuccess); + return; + } + + case UpdateState::MergeFailed: { + LOG(ERROR) << "Merge failed. Device may be corrupted."; + processor_->ActionComplete(this, ErrorCode::kDeviceCorrupted); + return; + } + + case UpdateState::Cancelled: { + // DeltaPerformer::ResetUpdateProgress failed, hence snapshots are + // not deleted to avoid inconsistency. + // Nothing can be done here; just try next time. + ErrorCode error_code = + cancel_failed_ ? ErrorCode::kError : ErrorCode::kSuccess; + processor_->ActionComplete(this, error_code); + return; + } + + default: { + // Protobuf has some reserved enum values, so a default case is needed. + LOG(FATAL) << "SnapshotManager::ProcessUpdateState returns " + << static_cast<int32_t>(state); + } + } +} + +bool CleanupPreviousUpdateAction::OnMergePercentageUpdate() { + double percentage = 0.0; + snapshot_->GetUpdateState(&percentage); + if (delegate_) { + // libsnapshot uses [0, 100] percentage but update_engine uses [0, 1]. + delegate_->OnCleanupProgressUpdate(percentage / 100); + } + + // Log if percentage increments by at least 1. + if (last_percentage_ < static_cast<unsigned int>(percentage)) { + last_percentage_ = percentage; + LOG(INFO) << "Waiting for merge to complete: " << last_percentage_ << "%."; + } + + // Do not continue to wait for merge. Instead, let ProcessUpdateState + // return Merging directly so that we can ScheduleWaitForMerge() in + // MessageLoop. + return false; +} + +bool CleanupPreviousUpdateAction::BeforeCancel() { + if (DeltaPerformer::ResetUpdateProgress( + prefs_, + false /* quick */, + false /* skip dynamic partitions metadata*/)) { + return true; + } + + // ResetUpdateProgress might not work on stub prefs. Do additional checks. + LOG(WARNING) << "ProcessUpdateState returns Cancelled but cleanup failed."; + + std::string val; + ignore_result(prefs_->GetString(kPrefsDynamicPartitionMetadataUpdated, &val)); + if (val.empty()) { + LOG(INFO) << kPrefsDynamicPartitionMetadataUpdated + << " is empty, assuming successful cleanup"; + return true; + } + LOG(WARNING) + << kPrefsDynamicPartitionMetadataUpdated << " is " << val + << ", not deleting snapshots even though UpdateState is Cancelled."; + cancel_failed_ = true; + return false; +} + +void CleanupPreviousUpdateAction::InitiateMergeAndWait() { + TEST_AND_RETURN(running_); + LOG(INFO) << "Attempting to initiate merge."; + // suspend the VAB merge when running a DSU + if (GetBoolProperty("ro.gsid.image_running", false)) { + LOG(WARNING) << "Suspend the VAB merge when running a DSU."; + processor_->ActionComplete(this, ErrorCode::kError); + return; + } + + uint64_t cow_file_size; + if (snapshot_->InitiateMerge(&cow_file_size)) { + merge_stats_->set_cow_file_size(cow_file_size); + WaitForMergeOrSchedule(); + return; + } + + LOG(WARNING) << "InitiateMerge failed."; + auto state = snapshot_->GetUpdateState(); + merge_stats_->set_state(state); + if (state == UpdateState::Unverified) { + // We are stuck at unverified state. This can happen if the update has + // been applied, but it has not even been attempted yet (in libsnapshot, + // rollback indicator does not exist); for example, if update_engine + // restarts before the device reboots, then this state may be reached. + // Nothing should be done here. + LOG(WARNING) << "InitiateMerge leaves the device at " + << "UpdateState::Unverified. (Did update_engine " + << "restarted?)"; + processor_->ActionComplete(this, ErrorCode::kSuccess); + return; + } + + // State does seems to be advanced. + // It is possibly racy. For example, on a userdebug build, the user may + // manually initiate a merge with snapshotctl between last time + // update_engine checks UpdateState. Hence, just call + // WaitForMergeOrSchedule one more time. + LOG(WARNING) << "IniitateMerge failed but GetUpdateState returned " + << android::snapshot::UpdateState_Name(state) + << ", try to wait for merge again."; + WaitForMergeOrSchedule(); + return; +} + +void CleanupPreviousUpdateAction::ReportMergeStats() { + auto result = merge_stats_->Finish(); + if (result == nullptr) { + LOG(WARNING) << "Not reporting merge stats because " + "SnapshotMergeStats::Finish failed."; + return; + } + +#ifdef __ANDROID_RECOVERY__ + LOG(INFO) << "Skip reporting merge stats in recovery."; +#else + const auto& report = result->report(); + + if (report.state() == UpdateState::None || + report.state() == UpdateState::Initiated || + report.state() == UpdateState::Unverified) { + LOG(INFO) << "Not reporting merge stats because state is " + << android::snapshot::UpdateState_Name(report.state()); + return; + } + + auto passed_ms = std::chrono::duration_cast<std::chrono::milliseconds>( + result->merge_time()); + + bool vab_retrofit = boot_control_->GetDynamicPartitionControl() + ->GetVirtualAbFeatureFlag() + .IsRetrofit(); + + LOG(INFO) << "Reporting merge stats: " + << android::snapshot::UpdateState_Name(report.state()) << " in " + << passed_ms.count() << "ms (resumed " << report.resume_count() + << " times), using " << report.cow_file_size() + << " bytes of COW image."; + android::util::stats_write(android::util::SNAPSHOT_MERGE_REPORTED, + static_cast<int32_t>(report.state()), + static_cast<int64_t>(passed_ms.count()), + static_cast<int32_t>(report.resume_count()), + vab_retrofit, + static_cast<int64_t>(report.cow_file_size())); +#endif +} + +} // namespace chromeos_update_engine diff --git a/cleanup_previous_update_action.h b/cleanup_previous_update_action.h new file mode 100644 index 00000000..6f6ce078 --- /dev/null +++ b/cleanup_previous_update_action.h @@ -0,0 +1,95 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_H_ +#define UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_H_ + +#include <chrono> // NOLINT(build/c++11) -- for merge times +#include <memory> +#include <string> + +#include <brillo/message_loops/message_loop.h> +#include <libsnapshot/snapshot.h> +#include <libsnapshot/snapshot_stats.h> + +#include "update_engine/common/action.h" +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/cleanup_previous_update_action_delegate.h" +#include "update_engine/common/error_code.h" +#include "update_engine/common/prefs_interface.h" + +namespace chromeos_update_engine { + +class CleanupPreviousUpdateAction; + +template <> +class ActionTraits<CleanupPreviousUpdateAction> { + public: + typedef NoneType InputObjectType; + typedef NoneType OutputObjectType; +}; + +// On Android Virtual A/B devices, clean up snapshots from previous update +// attempt. See DynamicPartitionControlAndroid::CleanupSuccessfulUpdate. +class CleanupPreviousUpdateAction : public Action<CleanupPreviousUpdateAction> { + public: + CleanupPreviousUpdateAction( + PrefsInterface* prefs, + BootControlInterface* boot_control, + android::snapshot::ISnapshotManager* snapshot, + CleanupPreviousUpdateActionDelegateInterface* delegate); + + void PerformAction() override; + void SuspendAction() override; + void ResumeAction() override; + void TerminateProcessing() override; + void ActionCompleted(ErrorCode error_code) override; + std::string Type() const override; + static std::string StaticType(); + typedef ActionTraits<CleanupPreviousUpdateAction>::InputObjectType + InputObjectType; + typedef ActionTraits<CleanupPreviousUpdateAction>::OutputObjectType + OutputObjectType; + + private: + PrefsInterface* prefs_; + BootControlInterface* boot_control_; + android::snapshot::ISnapshotManager* snapshot_; + CleanupPreviousUpdateActionDelegateInterface* delegate_; + std::unique_ptr<android::snapshot::AutoDevice> metadata_device_; + bool running_{false}; + bool cancel_failed_{false}; + unsigned int last_percentage_{0}; + android::snapshot::ISnapshotMergeStats* merge_stats_; + + void StartActionInternal(); + void ScheduleWaitBootCompleted(); + void WaitBootCompletedOrSchedule(); + void ScheduleWaitMarkBootSuccessful(); + void CheckSlotMarkedSuccessfulOrSchedule(); + void ScheduleWaitForMerge(); + void WaitForMergeOrSchedule(); + void InitiateMergeAndWait(); + void ReportMergeStats(); + + // Callbacks to ProcessUpdateState. + bool OnMergePercentageUpdate(); + bool BeforeCancel(); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_H_ diff --git a/client_library/include/update_engine/update_status.h b/client_library/include/update_engine/update_status.h index b1cf1f85..043a36e5 100644 --- a/client_library/include/update_engine/update_status.h +++ b/client_library/include/update_engine/update_status.h @@ -48,6 +48,7 @@ enum class UpdateStatus { // Broadcast this state when an update aborts because user preferences do not // allow updates, e.g. over cellular network. NEED_PERMISSION_TO_UPDATE = 10, + CLEANUP_PREVIOUS_UPDATE = 11, // This value is exclusively used in Chrome. DO NOT define nor use it. // TODO(crbug.com/977320): Remove this value from chrome by refactoring the diff --git a/common/action.h b/common/action.h index 9e2f5ff6..fd82c2d5 100644 --- a/common/action.h +++ b/common/action.h @@ -222,6 +222,17 @@ class Action : public AbstractAction { out_pipe_; }; +// An action that does nothing and completes with kSuccess immediately. +class NoOpAction : public AbstractAction { + public: + ~NoOpAction() override {} + void PerformAction() override { + processor_->ActionComplete(this, ErrorCode::kSuccess); + } + static std::string StaticType() { return "NoOpAction"; } + std::string Type() const override { return StaticType(); } +}; + }; // namespace chromeos_update_engine #endif // UPDATE_ENGINE_COMMON_ACTION_H_ diff --git a/common/action_pipe.h b/common/action_pipe.h index 0c98ee13..4c568126 100644 --- a/common/action_pipe.h +++ b/common/action_pipe.h @@ -79,6 +79,8 @@ class ActionPipe { private: ObjectType contents_; + // Give unit test access + friend class DownloadActionTest; // The ctor is private. This is because this class should construct itself // via the static Bond() method. diff --git a/common/action_processor.h b/common/action_processor.h index 735a1063..ad98cc9c 100644 --- a/common/action_processor.h +++ b/common/action_processor.h @@ -89,7 +89,7 @@ class ActionProcessor { // But this call deletes the action if there no other object has a reference // to it, so in that case, the caller should not try to access any of its // member variables after this call. - void ActionComplete(AbstractAction* actionptr, ErrorCode code); + virtual void ActionComplete(AbstractAction* actionptr, ErrorCode code); private: FRIEND_TEST(ActionProcessorTest, ChainActionsTest); diff --git a/common/boot_control_interface.h b/common/boot_control_interface.h index 392d7851..c93de5c5 100644 --- a/common/boot_control_interface.h +++ b/common/boot_control_interface.h @@ -25,6 +25,9 @@ #include <base/callback.h> #include <base/macros.h> +#include "update_engine/common/dynamic_partition_control_interface.h" +#include "update_engine/update_metadata.pb.h" + namespace chromeos_update_engine { // The abstract boot control interface defines the interaction with the @@ -35,19 +38,6 @@ class BootControlInterface { public: using Slot = unsigned int; - struct PartitionMetadata { - struct Partition { - std::string name; - uint64_t size; - }; - struct Group { - std::string name; - uint64_t size; - std::vector<Partition> partitions; - }; - std::vector<Group> groups; - }; - static const Slot kInvalidSlot = UINT_MAX; virtual ~BootControlInterface() = default; @@ -67,9 +57,20 @@ class BootControlInterface { // The |slot| number must be between 0 and GetNumSlots() - 1 and the // |partition_name| is a platform-specific name that identifies a partition on // every slot. In order to access the dynamic partitions in the target slot, - // InitPartitionMetadata() must be called (once per payload) prior to calling - // this function. On success, returns true and stores the block device in - // |device|. + // GetDynamicPartitionControl()->PreparePartitionsForUpdate() must be called + // (with |update| == true for the first time for a payload, and |false| for + // for the rest of the times) prior to calling this function. + // The handling may be different based on whether the partition is included + // in the update payload. On success, returns true; and stores the block + // device in |device|, if the partition is dynamic in |is_dynamic|. + virtual bool GetPartitionDevice(const std::string& partition_name, + Slot slot, + bool not_in_payload, + std::string* device, + bool* is_dynamic) const = 0; + + // Overload of the above function. We assume the partition is always included + // in the payload. virtual bool GetPartitionDevice(const std::string& partition_name, Slot slot, std::string* device) const = 0; @@ -94,17 +95,11 @@ class BootControlInterface { // of the operation. virtual bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) = 0; - // Initializes the metadata of the underlying partitions for a given |slot| - // and sets up the states for accessing dynamic partitions. - // |partition_metadata| will be written to the specified |slot| if - // |update_metadata| is set. - virtual bool InitPartitionMetadata( - Slot slot, - const PartitionMetadata& partition_metadata, - bool update_metadata) = 0; - - // Do necessary clean-up operations after the whole update. - virtual void Cleanup() = 0; + // Check if |slot| is marked boot successfully. + virtual bool IsSlotMarkedSuccessful(Slot slot) const = 0; + + // Return the dynamic partition control interface. + virtual DynamicPartitionControlInterface* GetDynamicPartitionControl() = 0; // Return a human-readable slot name used for logging. static std::string SlotName(Slot slot) { diff --git a/common/boot_control_stub.cc b/common/boot_control_stub.cc index 0fe8a989..907f6707 100644 --- a/common/boot_control_stub.cc +++ b/common/boot_control_stub.cc @@ -15,6 +15,7 @@ // #include "update_engine/common/boot_control_stub.h" +#include "update_engine/common/dynamic_partition_control_stub.h" #include <base/logging.h> @@ -22,6 +23,9 @@ using std::string; namespace chromeos_update_engine { +BootControlStub::BootControlStub() + : dynamic_partition_control_(new DynamicPartitionControlStub()) {} + unsigned int BootControlStub::GetNumSlots() const { return 0; } @@ -31,6 +35,15 @@ BootControlInterface::Slot BootControlStub::GetCurrentSlot() const { return 0; } +bool BootControlStub::GetPartitionDevice(const std::string& partition_name, + BootControlInterface::Slot slot, + bool not_in_payload, + std::string* device, + bool* is_dynamic) const { + LOG(ERROR) << __FUNCTION__ << " should never be called."; + return false; +} + bool BootControlStub::GetPartitionDevice(const string& partition_name, Slot slot, string* device) const { @@ -59,16 +72,14 @@ bool BootControlStub::MarkBootSuccessfulAsync( return false; } -bool BootControlStub::InitPartitionMetadata( - Slot slot, - const PartitionMetadata& partition_metadata, - bool update_metadata) { +bool BootControlStub::IsSlotMarkedSuccessful(Slot slot) const { LOG(ERROR) << __FUNCTION__ << " should never be called."; return false; } -void BootControlStub::Cleanup() { - LOG(ERROR) << __FUNCTION__ << " should never be called."; +DynamicPartitionControlInterface* +BootControlStub::GetDynamicPartitionControl() { + return dynamic_partition_control_.get(); } } // namespace chromeos_update_engine diff --git a/common/boot_control_stub.h b/common/boot_control_stub.h index 8dfaffc6..a1bdb965 100644 --- a/common/boot_control_stub.h +++ b/common/boot_control_stub.h @@ -17,9 +17,11 @@ #ifndef UPDATE_ENGINE_COMMON_BOOT_CONTROL_STUB_H_ #define UPDATE_ENGINE_COMMON_BOOT_CONTROL_STUB_H_ +#include <memory> #include <string> #include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/dynamic_partition_control_interface.h" namespace chromeos_update_engine { @@ -32,25 +34,30 @@ namespace chromeos_update_engine { // implementation is in use. class BootControlStub : public BootControlInterface { public: - BootControlStub() = default; + BootControlStub(); ~BootControlStub() = default; // BootControlInterface overrides. unsigned int GetNumSlots() const override; BootControlInterface::Slot GetCurrentSlot() const override; bool GetPartitionDevice(const std::string& partition_name, + Slot slot, + bool not_in_payload, + std::string* device, + bool* is_dynamic) const override; + bool GetPartitionDevice(const std::string& partition_name, BootControlInterface::Slot slot, std::string* device) const override; bool IsSlotBootable(BootControlInterface::Slot slot) const override; bool MarkSlotUnbootable(BootControlInterface::Slot slot) override; bool SetActiveBootSlot(BootControlInterface::Slot slot) override; bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override; - bool InitPartitionMetadata(Slot slot, - const PartitionMetadata& partition_metadata, - bool update_metadata) override; - void Cleanup() override; + bool IsSlotMarkedSuccessful(BootControlInterface::Slot slot) const override; + DynamicPartitionControlInterface* GetDynamicPartitionControl() override; private: + std::unique_ptr<DynamicPartitionControlInterface> dynamic_partition_control_; + DISALLOW_COPY_AND_ASSIGN(BootControlStub); }; diff --git a/common/cleanup_previous_update_action_delegate.h b/common/cleanup_previous_update_action_delegate.h new file mode 100644 index 00000000..7dad9c50 --- /dev/null +++ b/common/cleanup_previous_update_action_delegate.h @@ -0,0 +1,32 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_DELEGETE_H_ +#define UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_DELEGETE_H_ + +namespace chromeos_update_engine { + +// Delegate interface for CleanupPreviousUpdateAction. +class CleanupPreviousUpdateActionDelegateInterface { + public: + virtual ~CleanupPreviousUpdateActionDelegateInterface() {} + // |progress| is within [0, 1] + virtual void OnCleanupProgressUpdate(double progress) = 0; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_DELEGETE_H_ diff --git a/common/constants.cc b/common/constants.cc index ad511d55..8883668a 100644 --- a/common/constants.cc +++ b/common/constants.cc @@ -110,6 +110,7 @@ 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"; @@ -130,8 +131,7 @@ 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 post install, this will only be -// honored if we're resuming an update and post install has already succeeded. +// 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"; diff --git a/common/constants.h b/common/constants.h index 446b147a..36851020 100644 --- a/common/constants.h +++ b/common/constants.h @@ -105,6 +105,7 @@ extern const char kPrefsUrlSwitchCount[]; extern const char kPrefsVerityWritten[]; extern const char kPrefsWallClockScatteringWaitPeriod[]; extern const char kPrefsWallClockStagingWaitPeriod[]; +extern const char kPrefsManifestBytes[]; // Keys used when storing and loading payload properties. extern const char kPayloadPropertyFileSize[]; @@ -215,6 +216,9 @@ const int kDownloadP2PMaxRetryCount = 5; const int kDownloadConnectTimeoutSeconds = 30; const int kDownloadP2PConnectTimeoutSeconds = 5; +// Size in bytes of SHA256 hash. +const int kSHA256Size = 32; + } // namespace chromeos_update_engine #endif // UPDATE_ENGINE_COMMON_CONSTANTS_H_ diff --git a/common/dynamic_partition_control_interface.h b/common/dynamic_partition_control_interface.h new file mode 100644 index 00000000..7c2d0b0c --- /dev/null +++ b/common/dynamic_partition_control_interface.h @@ -0,0 +1,144 @@ +// +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_COMMON_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_ +#define UPDATE_ENGINE_COMMON_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_ + +#include <stdint.h> + +#include <memory> +#include <string> +#include <vector> + +#include "update_engine/common/action.h" +#include "update_engine/common/cleanup_previous_update_action_delegate.h" +#include "update_engine/common/error_code.h" +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { + +struct FeatureFlag { + enum class Value { NONE = 0, RETROFIT, LAUNCH }; + constexpr explicit FeatureFlag(Value value) : value_(value) {} + constexpr bool IsEnabled() const { return value_ != Value::NONE; } + constexpr bool IsRetrofit() const { return value_ == Value::RETROFIT; } + constexpr bool IsLaunch() const { return value_ == Value::LAUNCH; } + + private: + Value value_; +}; + +class BootControlInterface; +class PrefsInterface; + +class DynamicPartitionControlInterface { + public: + virtual ~DynamicPartitionControlInterface() = default; + + // Return the feature flags of dynamic partitions on this device. + // Return RETROFIT iff dynamic partitions is retrofitted on this device, + // LAUNCH iff this device is launched with dynamic partitions, + // NONE iff dynamic partitions is disabled on this device. + virtual FeatureFlag GetDynamicPartitionsFeatureFlag() = 0; + + // Return the feature flags of Virtual A/B on this device. + virtual FeatureFlag GetVirtualAbFeatureFlag() = 0; + + // Attempt to optimize |operation|. + // If successful, |optimized| contains an operation with extents that + // needs to be written. + // If failed, no optimization is available, and caller should perform + // |operation| directly. + // |partition_name| should not have the slot suffix; implementation of + // DynamicPartitionControlInterface checks partition at the target slot + // previously set with PreparePartitionsForUpdate(). + virtual bool OptimizeOperation(const std::string& partition_name, + const InstallOperation& operation, + InstallOperation* optimized) = 0; + + // Do necessary cleanups before destroying the object. + virtual void Cleanup() = 0; + + // Prepare all partitions for an update specified in |manifest|. + // This is needed before calling MapPartitionOnDeviceMapper(), otherwise the + // device would be mapped in an inconsistent way. + // If |update| is set, create snapshots and writes super partition metadata. + // If |required_size| is not null and call fails due to insufficient space, + // |required_size| will be set to total free space required on userdata + // partition to apply the update. Otherwise (call succeeds, or fails + // due to other errors), |required_size| is set to zero. + virtual bool PreparePartitionsForUpdate(uint32_t source_slot, + uint32_t target_slot, + const DeltaArchiveManifest& manifest, + bool update, + uint64_t* required_size) = 0; + + // After writing to new partitions, before rebooting into the new slot, call + // this function to indicate writes to new partitions are done. + virtual bool FinishUpdate(bool powerwash_required) = 0; + + // Get an action to clean up previous update. + // Return NoOpAction on non-Virtual A/B devices. + // Before applying the next update, run this action to clean up previous + // update files. This function blocks until delta files are merged into + // current OS partitions and finished cleaning up. + // - If successful, action completes with kSuccess. + // - If any error, but caller should retry after reboot, action completes with + // kError. + // - If any irrecoverable failures, action completes with kDeviceCorrupted. + // + // See ResetUpdate for differences between CleanuPreviousUpdateAction and + // ResetUpdate. + virtual std::unique_ptr<AbstractAction> GetCleanupPreviousUpdateAction( + BootControlInterface* boot_control, + PrefsInterface* prefs, + CleanupPreviousUpdateActionDelegateInterface* delegate) = 0; + + // Called after an unwanted payload has been successfully applied and the + // device has not yet been rebooted. + // + // For snapshot updates (Virtual A/B), it calls + // DeltaPerformer::ResetUpdateProgress(false /* quick */) and + // frees previously allocated space; the next update will need to be + // started over. + // + // Note: CleanupPreviousUpdateAction does not do anything if an update is in + // progress, while ResetUpdate() forcefully free previously + // allocated space for snapshot updates. + virtual bool ResetUpdate(PrefsInterface* prefs) = 0; + + // Reads the dynamic partitions metadata from the current slot, and puts the + // name of the dynamic partitions with the current suffix to |partitions|. + // Returns true on success. + virtual bool ListDynamicPartitionsForSlot( + uint32_t current_slot, std::vector<std::string>* partitions) = 0; + + // Finds a possible location that list all block devices by name; and puts + // the result in |path|. Returns true on success. + // Sample result: /dev/block/by-name/ + virtual bool GetDeviceDir(std::string* path) = 0; + + // Verifies that the untouched dynamic partitions in the target metadata have + // the same extents as the source metadata. + virtual bool VerifyExtentsForUntouchedPartitions( + uint32_t source_slot, + uint32_t target_slot, + const std::vector<std::string>& partitions) = 0; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_ diff --git a/common/dynamic_partition_control_stub.cc b/common/dynamic_partition_control_stub.cc new file mode 100644 index 00000000..5a8ca434 --- /dev/null +++ b/common/dynamic_partition_control_stub.cc @@ -0,0 +1,86 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <stdint.h> + +#include <memory> +#include <string> + +#include <base/logging.h> + +#include "update_engine/common/dynamic_partition_control_stub.h" + +namespace chromeos_update_engine { + +FeatureFlag DynamicPartitionControlStub::GetDynamicPartitionsFeatureFlag() { + return FeatureFlag(FeatureFlag::Value::NONE); +} + +FeatureFlag DynamicPartitionControlStub::GetVirtualAbFeatureFlag() { + return FeatureFlag(FeatureFlag::Value::NONE); +} + +bool DynamicPartitionControlStub::OptimizeOperation( + const std::string& partition_name, + const InstallOperation& operation, + InstallOperation* optimized) { + return false; +} + +void DynamicPartitionControlStub::Cleanup() {} + +bool DynamicPartitionControlStub::PreparePartitionsForUpdate( + uint32_t source_slot, + uint32_t target_slot, + const DeltaArchiveManifest& manifest, + bool update, + uint64_t* required_size) { + return true; +} + +bool DynamicPartitionControlStub::FinishUpdate(bool powerwash_required) { + return true; +} + +std::unique_ptr<AbstractAction> +DynamicPartitionControlStub::GetCleanupPreviousUpdateAction( + BootControlInterface* boot_control, + PrefsInterface* prefs, + CleanupPreviousUpdateActionDelegateInterface* delegate) { + return std::make_unique<NoOpAction>(); +} + +bool DynamicPartitionControlStub::ResetUpdate(PrefsInterface* prefs) { + return false; +} + +bool DynamicPartitionControlStub::ListDynamicPartitionsForSlot( + uint32_t current_slot, std::vector<std::string>* partitions) { + return true; +} + +bool DynamicPartitionControlStub::GetDeviceDir(std::string* path) { + return true; +} + +bool DynamicPartitionControlStub::VerifyExtentsForUntouchedPartitions( + uint32_t source_slot, + uint32_t target_slot, + const std::vector<std::string>& partitions) { + return true; +} + +} // namespace chromeos_update_engine diff --git a/common/dynamic_partition_control_stub.h b/common/dynamic_partition_control_stub.h new file mode 100644 index 00000000..94dba1bc --- /dev/null +++ b/common/dynamic_partition_control_stub.h @@ -0,0 +1,63 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_COMMON_DYNAMIC_PARTITION_CONTROL_STUB_H_ +#define UPDATE_ENGINE_COMMON_DYNAMIC_PARTITION_CONTROL_STUB_H_ + +#include <stdint.h> + +#include <memory> +#include <string> +#include <vector> + +#include "update_engine/common/dynamic_partition_control_interface.h" + +namespace chromeos_update_engine { + +class DynamicPartitionControlStub : public DynamicPartitionControlInterface { + public: + FeatureFlag GetDynamicPartitionsFeatureFlag() override; + FeatureFlag GetVirtualAbFeatureFlag() override; + bool OptimizeOperation(const std::string& partition_name, + const InstallOperation& operation, + InstallOperation* optimized) override; + void Cleanup() override; + bool PreparePartitionsForUpdate(uint32_t source_slot, + uint32_t target_slot, + const DeltaArchiveManifest& manifest, + bool update, + uint64_t* required_size) override; + + bool FinishUpdate(bool powerwash_required) override; + std::unique_ptr<AbstractAction> GetCleanupPreviousUpdateAction( + BootControlInterface* boot_control, + PrefsInterface* prefs, + CleanupPreviousUpdateActionDelegateInterface* delegate) override; + bool ResetUpdate(PrefsInterface* prefs) override; + + bool ListDynamicPartitionsForSlot( + uint32_t current_slot, std::vector<std::string>* partitions) override; + bool GetDeviceDir(std::string* path) override; + + bool VerifyExtentsForUntouchedPartitions( + uint32_t source_slot, + uint32_t target_slot, + const std::vector<std::string>& partitions) override; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_DYNAMIC_PARTITION_CONTROL_STUB_H_ diff --git a/common/error_code.h b/common/error_code.h index 7acb3b62..7d9cfff8 100644 --- a/common/error_code.h +++ b/common/error_code.h @@ -83,7 +83,9 @@ enum class ErrorCode : int { kInternalLibCurlError = 57, kUnresolvedHostError = 58, kUnresolvedHostRecovered = 59, - kPackageExcludedFromUpdate = 60, + kNotEnoughSpace = 60, + kDeviceCorrupted = 61, + kPackageExcludedFromUpdate = 62, // VERY IMPORTANT! When adding new error codes: // diff --git a/common/error_code_utils.cc b/common/error_code_utils.cc index 55d876fc..cda4c7ec 100644 --- a/common/error_code_utils.cc +++ b/common/error_code_utils.cc @@ -167,6 +167,10 @@ string ErrorCodeToString(ErrorCode code) { return "ErrorCode::kUnresolvedHostError"; case ErrorCode::kUnresolvedHostRecovered: return "ErrorCode::kUnresolvedHostRecovered"; + case ErrorCode::kNotEnoughSpace: + return "ErrorCode::kNotEnoughSpace"; + case ErrorCode::kDeviceCorrupted: + return "ErrorCode::kDeviceCorrupted"; case ErrorCode::kPackageExcludedFromUpdate: return "ErrorCode::kPackageExcludedFromUpdate"; // Don't add a default case to let the compiler warn about newly added diff --git a/common/fake_boot_control.h b/common/fake_boot_control.h index 3d650758..98b93e66 100644 --- a/common/fake_boot_control.h +++ b/common/fake_boot_control.h @@ -18,12 +18,14 @@ #define UPDATE_ENGINE_COMMON_FAKE_BOOT_CONTROL_H_ #include <map> +#include <memory> #include <string> #include <vector> #include <base/time/time.h> #include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/dynamic_partition_control_stub.h" namespace chromeos_update_engine { @@ -34,6 +36,8 @@ class FakeBootControl : public BootControlInterface { SetNumSlots(num_slots_); // The current slot should be bootable. is_bootable_[current_slot_] = true; + + dynamic_partition_control_.reset(new DynamicPartitionControlStub()); } // BootControlInterface overrides. @@ -44,16 +48,27 @@ class FakeBootControl : public BootControlInterface { bool GetPartitionDevice(const std::string& partition_name, BootControlInterface::Slot slot, - std::string* device) const override { + bool not_in_payload, + std::string* device, + bool* is_dynamic) const override { if (slot >= num_slots_) return false; auto part_it = devices_[slot].find(partition_name); if (part_it == devices_[slot].end()) return false; *device = part_it->second; + if (is_dynamic != nullptr) { + *is_dynamic = false; + } return true; } + bool GetPartitionDevice(const std::string& partition_name, + BootControlInterface::Slot slot, + std::string* device) const override { + return GetPartitionDevice(partition_name, slot, false, device, nullptr); + } + bool IsSlotBootable(BootControlInterface::Slot slot) const override { return slot < num_slots_ && is_bootable_[slot]; } @@ -70,22 +85,20 @@ class FakeBootControl : public BootControlInterface { bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override { // We run the callback directly from here to avoid having to setup a message // loop in the test environment. + is_marked_successful_[GetCurrentSlot()] = true; callback.Run(true); return true; } - bool InitPartitionMetadata(Slot slot, - const PartitionMetadata& partition_metadata, - bool update_metadata) override { - return true; + bool IsSlotMarkedSuccessful(Slot slot) const override { + return slot < num_slots_ && is_marked_successful_[slot]; } - void Cleanup() override {} - // Setters void SetNumSlots(unsigned int num_slots) { num_slots_ = num_slots; is_bootable_.resize(num_slots_, false); + is_marked_successful_.resize(num_slots_, false); devices_.resize(num_slots_); } @@ -103,13 +116,20 @@ class FakeBootControl : public BootControlInterface { is_bootable_[slot] = bootable; } + DynamicPartitionControlInterface* GetDynamicPartitionControl() override { + return dynamic_partition_control_.get(); + } + private: BootControlInterface::Slot num_slots_{2}; BootControlInterface::Slot current_slot_{0}; std::vector<bool> is_bootable_; + std::vector<bool> is_marked_successful_; std::vector<std::map<std::string, std::string>> devices_; + std::unique_ptr<DynamicPartitionControlInterface> dynamic_partition_control_; + DISALLOW_COPY_AND_ASSIGN(FakeBootControl); }; diff --git a/common/fake_hardware.h b/common/fake_hardware.h index 6604534c..82382ffd 100644 --- a/common/fake_hardware.h +++ b/common/fake_hardware.h @@ -19,10 +19,13 @@ #include <map> #include <string> +#include <utility> #include <base/time/time.h> +#include "update_engine/common/error_code.h" #include "update_engine/common/hardware_interface.h" +#include "update_engine/common/utils.h" namespace chromeos_update_engine { @@ -132,6 +135,8 @@ class FakeHardware : public HardwareInterface { int64_t GetBuildTimestamp() const override { return build_timestamp_; } + bool AllowDowngrade() const override { return false; } + bool GetFirstActiveOmahaPingSent() const override { return first_active_omaha_ping_sent_; } @@ -197,12 +202,27 @@ class FakeHardware : public HardwareInterface { build_timestamp_ = build_timestamp; } + void SetWarmReset(bool warm_reset) override { warm_reset_ = warm_reset; } + // Getters to verify state. int GetMaxKernelKeyRollforward() const { return kernel_max_rollforward_; } bool GetIsRollbackPowerwashScheduled() const { return powerwash_scheduled_ && save_rollback_data_; } + std::string GetVersionForLogging( + const std::string& partition_name) const override { + return partition_timestamps_[partition_name]; + } + void SetVersion(const std::string& partition_name, std::string timestamp) { + partition_timestamps_[partition_name] = std::move(timestamp); + } + ErrorCode IsPartitionUpdateValid( + const std::string& partition_name, + const std::string& new_version) const override { + const auto old_version = GetVersionForLogging(partition_name); + return utils::IsTimestampNewer(old_version, new_version); + } private: bool is_official_build_{true}; @@ -225,6 +245,8 @@ class FakeHardware : public HardwareInterface { bool save_rollback_data_{false}; int64_t build_timestamp_{0}; bool first_active_omaha_ping_sent_{false}; + bool warm_reset_{false}; + mutable std::map<std::string, std::string> partition_timestamps_; DISALLOW_COPY_AND_ASSIGN(FakeHardware); }; diff --git a/common/file_fetcher.cc b/common/file_fetcher.cc index 3836e548..7134fd69 100644 --- a/common/file_fetcher.cc +++ b/common/file_fetcher.cc @@ -43,8 +43,9 @@ namespace chromeos_update_engine { // static bool FileFetcher::SupportedUrl(const string& url) { // Note that we require the file path to start with a "/". - return base::StartsWith( - url, "file:///", base::CompareCase::INSENSITIVE_ASCII); + return ( + base::StartsWith(url, "file:///", base::CompareCase::INSENSITIVE_ASCII) || + base::StartsWith(url, "fd://", base::CompareCase::INSENSITIVE_ASCII)); } FileFetcher::~FileFetcher() { @@ -67,12 +68,20 @@ void FileFetcher::BeginTransfer(const string& url) { return; } - string file_path = url.substr(strlen("file://")); - stream_ = - brillo::FileStream::Open(base::FilePath(file_path), - brillo::Stream::AccessMode::READ, - brillo::FileStream::Disposition::OPEN_EXISTING, - nullptr); + string file_path; + + if (base::StartsWith(url, "fd://", base::CompareCase::INSENSITIVE_ASCII)) { + int fd = std::stoi(url.substr(strlen("fd://"))); + file_path = url; + stream_ = brillo::FileStream::FromFileDescriptor(fd, false, nullptr); + } else { + file_path = url.substr(strlen("file://")); + stream_ = + brillo::FileStream::Open(base::FilePath(file_path), + brillo::Stream::AccessMode::READ, + brillo::FileStream::Disposition::OPEN_EXISTING, + nullptr); + } if (!stream_) { LOG(ERROR) << "Couldn't open " << file_path; @@ -183,5 +192,4 @@ void FileFetcher::CleanUp() { transfer_in_progress_ = false; transfer_paused_ = false; } - } // namespace chromeos_update_engine diff --git a/common/hardware_interface.h b/common/hardware_interface.h index da9f10ef..b37b0074 100644 --- a/common/hardware_interface.h +++ b/common/hardware_interface.h @@ -25,6 +25,8 @@ #include <base/files/file_path.h> #include <base/time/time.h> +#include "update_engine/common/error_code.h" + namespace chromeos_update_engine { // The hardware interface allows access to the crossystem exposed properties, @@ -126,6 +128,10 @@ class HardwareInterface { // Returns the timestamp of the current OS build. virtual int64_t GetBuildTimestamp() const = 0; + // Returns true if the current OS build allows installing the payload with an + // older timestamp. + virtual bool AllowDowngrade() const = 0; + // Returns whether the first active ping was sent to Omaha at some point, and // that the value is persisted across recovery (and powerwash) once set with // |SetFirstActiveOmahaPingSent()|. @@ -134,6 +140,30 @@ class HardwareInterface { // Persist the fact that first active ping was sent to omaha and returns false // if failed to persist it. virtual bool SetFirstActiveOmahaPingSent() = 0; + + // If |warm_reset| is true, sets the warm reset to indicate a warm reset is + // needed on the next reboot. Otherwise, clears the flag. + virtual void SetWarmReset(bool warm_reset) = 0; + + // Return the version/timestamp for partition `partition_name`. + // Don't make any assumption about the formatting of returned string. + // Only used for logging/debugging purposes. + virtual std::string GetVersionForLogging( + const std::string& partition_name) const = 0; + + // Return true if and only if `new_version` is "newer" than the + // version number of partition `partition_name`. The notion of + // "newer" is defined by this function. Caller should not make + // any assumption about the underlying logic. + // Return: + // - kSuccess if update is valid. + // - kPayloadTimestampError if downgrade is detected + // - kDownloadManifestParseError if |new_version| has an incorrect format + // - Other error values if the source of error is known, or kError for + // a generic error on the device. + virtual ErrorCode IsPartitionUpdateValid( + const std::string& partition_name, + const std::string& new_version) const = 0; }; } // namespace chromeos_update_engine diff --git a/common/http_fetcher_unittest.cc b/common/http_fetcher_unittest.cc index 3ecb996f..9338087d 100644 --- a/common/http_fetcher_unittest.cc +++ b/common/http_fetcher_unittest.cc @@ -37,7 +37,11 @@ #include <brillo/message_loops/base_message_loop.h> #include <brillo/message_loops/message_loop.h> #include <brillo/message_loops/message_loop_utils.h> +#ifdef __CHROMEOS__ #include <brillo/process/process.h> +#else +#include <brillo/process.h> +#endif // __CHROMEOS__ #include <brillo/streams/file_stream.h> #include <brillo/streams/stream.h> #include <gtest/gtest.h> diff --git a/common/mock_action_processor.h b/common/mock_action_processor.h index 4c62109b..97857764 100644 --- a/common/mock_action_processor.h +++ b/common/mock_action_processor.h @@ -32,6 +32,8 @@ class MockActionProcessor : public ActionProcessor { MOCK_METHOD0(StartProcessing, void()); MOCK_METHOD1(EnqueueAction, void(AbstractAction* action)); + MOCK_METHOD2(ActionComplete, void(AbstractAction*, ErrorCode)); + // This is a legacy workaround described in: // https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md#legacy-workarounds-for-move-only-types-legacymoveonly void EnqueueAction(std::unique_ptr<AbstractAction> action) override { diff --git a/common/mock_http_fetcher.cc b/common/mock_http_fetcher.cc index 10e3b9ef..1b3cd7d7 100644 --- a/common/mock_http_fetcher.cc +++ b/common/mock_http_fetcher.cc @@ -22,6 +22,7 @@ #include <base/logging.h> #include <base/strings/string_util.h> #include <base/time/time.h> +#include <brillo/message_loops/message_loop.h> #include <gtest/gtest.h> // This is a mock implementation of HttpFetcher which is useful for testing. @@ -43,12 +44,12 @@ void MockHttpFetcher::BeginTransfer(const std::string& url) { SignalTransferComplete(); return; } - if (sent_size_ < data_.size()) + if (sent_offset_ < data_.size()) SendData(true); } void MockHttpFetcher::SendData(bool skip_delivery) { - if (fail_transfer_ || sent_size_ == data_.size()) { + if (fail_transfer_ || sent_offset_ == data_.size()) { SignalTransferComplete(); return; } @@ -60,19 +61,22 @@ void MockHttpFetcher::SendData(bool skip_delivery) { // Setup timeout callback even if the transfer is about to be completed in // order to get a call to |TransferComplete|. - if (timeout_id_ == MessageLoop::kTaskIdNull) { + if (timeout_id_ == MessageLoop::kTaskIdNull && delay_) { + CHECK(MessageLoop::current()); timeout_id_ = MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&MockHttpFetcher::TimeoutCallback, base::Unretained(this)), base::TimeDelta::FromMilliseconds(10)); } - if (!skip_delivery) { + if (!skip_delivery || !delay_) { const size_t chunk_size = - min(kMockHttpFetcherChunkSize, data_.size() - sent_size_); - sent_size_ += chunk_size; + min(kMockHttpFetcherChunkSize, data_.size() - sent_offset_); + sent_offset_ += chunk_size; + bytes_sent_ += chunk_size; CHECK(delegate_); - delegate_->ReceivedBytes(this, &data_[sent_size_ - chunk_size], chunk_size); + delegate_->ReceivedBytes( + this, &data_[sent_offset_ - chunk_size], chunk_size); } // We may get terminated and deleted right after |ReceivedBytes| call, so we // should not access any class member variable after this call. @@ -81,7 +85,7 @@ void MockHttpFetcher::SendData(bool skip_delivery) { void MockHttpFetcher::TimeoutCallback() { CHECK(!paused_); timeout_id_ = MessageLoop::kTaskIdNull; - CHECK_LE(sent_size_, data_.size()); + CHECK_LE(sent_offset_, data_.size()); // Same here, we should not access any member variable after this call. SendData(false); } @@ -90,10 +94,15 @@ void MockHttpFetcher::TimeoutCallback() { // The transfer cannot be resumed. void MockHttpFetcher::TerminateTransfer() { LOG(INFO) << "Terminating transfer."; - // Kill any timeout, it is ok to call with kTaskIdNull. - MessageLoop::current()->CancelTask(timeout_id_); - timeout_id_ = MessageLoop::kTaskIdNull; - delegate_->TransferTerminated(this); + // During testing, MessageLoop may or may not be available. + // So don't call CancelTask() unless necessary. + if (timeout_id_ != MessageLoop::kTaskIdNull) { + MessageLoop::current()->CancelTask(timeout_id_); + timeout_id_ = MessageLoop::kTaskIdNull; + } + if (delegate_) { + delegate_->TransferTerminated(this); + } } void MockHttpFetcher::SetHeader(const std::string& header_name, diff --git a/common/mock_http_fetcher.h b/common/mock_http_fetcher.h index 0f043190..ea5b83da 100644 --- a/common/mock_http_fetcher.h +++ b/common/mock_http_fetcher.h @@ -46,7 +46,7 @@ class MockHttpFetcher : public HttpFetcher { size_t size, ProxyResolver* proxy_resolver) : HttpFetcher(proxy_resolver), - sent_size_(0), + sent_offset_(0), timeout_id_(brillo::MessageLoop::kTaskIdNull), paused_(false), fail_transfer_(false), @@ -64,7 +64,7 @@ class MockHttpFetcher : public HttpFetcher { // Ignores this. void SetOffset(off_t offset) override { - sent_size_ = offset; + sent_offset_ = offset; if (delegate_) delegate_->SeekToOffset(offset); } @@ -76,8 +76,8 @@ class MockHttpFetcher : public HttpFetcher { void set_connect_timeout(int connect_timeout_seconds) override {} void set_max_retry_count(int max_retry_count) override {} - // Dummy: no bytes were downloaded. - size_t GetBytesDownloaded() override { return sent_size_; } + // No bytes were downloaded in the mock class. + size_t GetBytesDownloaded() override { return bytes_sent_; } // Begins the transfer if it hasn't already begun. void BeginTransfer(const std::string& url) override; @@ -113,6 +113,8 @@ class MockHttpFetcher : public HttpFetcher { const brillo::Blob& post_data() const { return post_data_; } + void set_delay(bool delay) { delay_ = delay; } + private: // Sends data to the delegate and sets up a timeout callback if needed. There // must be a delegate. If |skip_delivery| is true, no bytes will be delivered, @@ -129,8 +131,11 @@ class MockHttpFetcher : public HttpFetcher { // A full copy of the data we'll return to the delegate brillo::Blob data_; - // The number of bytes we've sent so far - size_t sent_size_; + // The current offset, marks the first byte that will be sent next + size_t sent_offset_{0}; + + // Total number of bytes transferred + size_t bytes_sent_{0}; // The extra headers set. std::map<std::string, std::string> extra_headers_; @@ -140,13 +145,16 @@ class MockHttpFetcher : public HttpFetcher { brillo::MessageLoop::TaskId timeout_id_; // True iff the fetcher is paused. - bool paused_; + bool paused_{false}; // Set to true if the transfer should fail. - bool fail_transfer_; + bool fail_transfer_{false}; // Set to true if BeginTransfer should EXPECT fail. - bool never_use_; + bool never_use_{false}; + + // Whether it should wait for 10ms before sending data to delegates + bool delay_{true}; DISALLOW_COPY_AND_ASSIGN(MockHttpFetcher); }; diff --git a/common/platform_constants.h b/common/platform_constants.h index 6eaa940e..243af69e 100644 --- a/common/platform_constants.h +++ b/common/platform_constants.h @@ -38,6 +38,10 @@ extern const char kOmahaPlatformName[]; // whole payload. extern const char kUpdatePayloadPublicKeyPath[]; +// Path to the location of the zip archive file that contains PEM encoded X509 +// certificates. e.g. 'system/etc/security/otacerts.zip'. +extern const char kUpdateCertificatesPath[]; + // Path to the directory containing all the SSL certificates accepted by // update_engine when sending requests to Omaha and the download server (if // HTTPS is used for that as well). diff --git a/common/platform_constants_android.cc b/common/platform_constants_android.cc index 9d8d30e8..f468c3ba 100644 --- a/common/platform_constants_android.cc +++ b/common/platform_constants_android.cc @@ -25,8 +25,8 @@ const char kOmahaDefaultAUTestURL[] = "https://clients2.google.com/service/update2/brillo"; const char kOmahaUpdaterID[] = "Brillo"; const char kOmahaPlatformName[] = "Brillo"; -const char kUpdatePayloadPublicKeyPath[] = - "/etc/update_engine/update-payload-key.pub.pem"; +const char kUpdatePayloadPublicKeyPath[] = ""; +const char kUpdateCertificatesPath[] = "/system/etc/security/otacerts.zip"; const char kCACertificatesPath[] = "/system/etc/security/cacerts_google"; // No deadline file API support on Android. const char kOmahaResponseDeadlineFile[] = ""; diff --git a/common/platform_constants_chromeos.cc b/common/platform_constants_chromeos.cc index f1ac4909..fe94a45a 100644 --- a/common/platform_constants_chromeos.cc +++ b/common/platform_constants_chromeos.cc @@ -27,6 +27,7 @@ const char kOmahaUpdaterID[] = "ChromeOSUpdateEngine"; const char kOmahaPlatformName[] = "Chrome OS"; const char kUpdatePayloadPublicKeyPath[] = "/usr/share/update_engine/update-payload-key.pub.pem"; +const char kUpdateCertificatesPath[] = ""; const char kCACertificatesPath[] = "/usr/share/chromeos-ca-certificates"; const char kOmahaResponseDeadlineFile[] = "/tmp/update-check-response-deadline"; // This directory is wiped during powerwash. diff --git a/common/subprocess.cc b/common/subprocess.cc index ff37472f..023017b9 100644 --- a/common/subprocess.cc +++ b/common/subprocess.cc @@ -32,11 +32,11 @@ #include <base/stl_util.h> #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> -#include <brillo/process/process.h> #include <brillo/secure_blob.h> #include "update_engine/common/utils.h" +using brillo::MessageLoop; using std::string; using std::unique_ptr; using std::vector; diff --git a/common/subprocess.h b/common/subprocess.h index e1a7ce33..179a5c51 100644 --- a/common/subprocess.h +++ b/common/subprocess.h @@ -30,8 +30,13 @@ #include <base/macros.h> #include <brillo/asynchronous_signal_handler_interface.h> #include <brillo/message_loops/message_loop.h> +#ifdef __CHROMEOS__ #include <brillo/process/process.h> #include <brillo/process/process_reaper.h> +#else +#include <brillo/process.h> +#include <brillo/process_reaper.h> +#endif // __CHROMEOS__ #include <gtest/gtest_prod.h> // for FRIEND_TEST // The Subprocess class is a singleton. It's used to spawn off a subprocess @@ -124,6 +129,7 @@ class Subprocess { // These are used to monitor the stdout of the running process, including // the stderr if it was redirected. std::unique_ptr<base::FileDescriptorWatcher::Controller> stdout_controller; + int stdout_fd{-1}; std::string stdout; }; diff --git a/common/subprocess_unittest.cc b/common/subprocess_unittest.cc index 19b24f45..b4d068f0 100644 --- a/common/subprocess_unittest.cc +++ b/common/subprocess_unittest.cc @@ -279,7 +279,7 @@ TEST_F(SubprocessTest, CancelTest) { // This test would leak a callback that runs when the child process exits // unless we wait for it to run. brillo::MessageLoopRunUntil( - &loop_, TimeDelta::FromSeconds(120), base::Bind([] { + &loop_, TimeDelta::FromSeconds(20), base::Bind([] { return Subprocess::Get().subprocess_records_.empty(); })); EXPECT_TRUE(Subprocess::Get().subprocess_records_.empty()); diff --git a/common/test_utils.cc b/common/test_utils.cc index 50b09624..bd69d039 100644 --- a/common/test_utils.cc +++ b/common/test_utils.cc @@ -37,6 +37,10 @@ #include <base/files/file_util.h> #include <base/logging.h> +#ifdef __ANDROID__ +#include <libdm/loop_control.h> +#endif + #include "update_engine/common/error_code_utils.h" #include "update_engine/common/utils.h" #include "update_engine/payload_consumer/file_writer.h" @@ -44,16 +48,7 @@ using std::set; using std::string; using std::vector; - -namespace { - -#ifdef __ANDROID__ -#define kLoopDevicePrefix "/dev/block/loop" -#else -#define kLoopDevicePrefix "/dev/loop" -#endif // __ANDROID__ - -} // namespace +using namespace std::chrono_literals; namespace chromeos_update_engine { @@ -112,17 +107,43 @@ bool WriteFileString(const string& path, const string& data) { return utils::WriteFile(path.c_str(), data.data(), data.size()); } -bool BindToUnusedLoopDevice(const string& filename, - bool writable, - string* out_lo_dev_name) { - CHECK(out_lo_dev_name); +bool SetLoopDeviceStatus(int loop_device_fd, + const std::string& filename, + int loop_number, + bool writable) { + struct loop_info64 device_info {}; + device_info.lo_offset = 0; + device_info.lo_sizelimit = 0; // 0 means whole file. + device_info.lo_flags = (writable ? 0 : LO_FLAGS_READ_ONLY); + device_info.lo_number = loop_number; + strncpy(reinterpret_cast<char*>(device_info.lo_file_name), + base::FilePath(filename).BaseName().value().c_str(), + LO_NAME_SIZE - 1); + device_info.lo_file_name[LO_NAME_SIZE - 1] = '\0'; + TEST_AND_RETURN_FALSE_ERRNO( + ioctl(loop_device_fd, LOOP_SET_STATUS64, &device_info) == 0); + if (writable) { + // Make sure loop device isn't read only. + int ro = 0; + if (ioctl(loop_device_fd, BLKROSET, &ro) != 0) { + PLOG(WARNING) << "Failed to mark loop device writable."; + } + } + + return true; +} + +bool BindToUnusedLoopDeviceLegacy(int data_fd, + const string& filename, + bool writable, + string* out_lo_dev_name) { // Get the next available loop-device. int control_fd = HANDLE_EINTR(open("/dev/loop-control", O_RDWR | O_LARGEFILE)); TEST_AND_RETURN_FALSE_ERRNO(control_fd >= 0); int loop_number = ioctl(control_fd, LOOP_CTL_GET_FREE); IGNORE_EINTR(close(control_fd)); - *out_lo_dev_name = kLoopDevicePrefix + std::to_string(loop_number); + *out_lo_dev_name = "/dev/loop" + std::to_string(loop_number); // Double check that the loop exists and is free. int loop_device_fd = @@ -146,32 +167,35 @@ bool BindToUnusedLoopDevice(const string& filename, return false; } - // Open our data file and assign it to the loop device. + // Assign the data fd to the loop device. + TEST_AND_RETURN_FALSE_ERRNO(ioctl(loop_device_fd, LOOP_SET_FD, data_fd) == 0); + return SetLoopDeviceStatus(loop_device_fd, filename, loop_number, writable); +} + +bool BindToUnusedLoopDevice(const string& filename, + bool writable, + string* out_lo_dev_name) { + CHECK(out_lo_dev_name); int data_fd = open(filename.c_str(), (writable ? O_RDWR : O_RDONLY) | O_LARGEFILE | O_CLOEXEC); TEST_AND_RETURN_FALSE_ERRNO(data_fd >= 0); ScopedFdCloser data_fd_closer(&data_fd); - TEST_AND_RETURN_FALSE_ERRNO(ioctl(loop_device_fd, LOOP_SET_FD, data_fd) == 0); - memset(&device_info, 0, sizeof(device_info)); - device_info.lo_offset = 0; - device_info.lo_sizelimit = 0; // 0 means whole file. - device_info.lo_flags = (writable ? 0 : LO_FLAGS_READ_ONLY); - device_info.lo_number = loop_number; - strncpy(reinterpret_cast<char*>(device_info.lo_file_name), - base::FilePath(filename).BaseName().value().c_str(), - LO_NAME_SIZE - 1); - device_info.lo_file_name[LO_NAME_SIZE - 1] = '\0'; - TEST_AND_RETURN_FALSE_ERRNO( - ioctl(loop_device_fd, LOOP_SET_STATUS64, &device_info) == 0); - if (writable) { - // Make sure loop device isn't read only. - int ro = 0; - if (ioctl(loop_device_fd, BLKROSET, &ro) != 0) { - PLOG(WARNING) << "Failed to mark loop device writable."; - } - } - return true; +#ifdef __ANDROID__ + // Use libdm to bind a free loop device. The library internally handles the + // race condition. + android::dm::LoopControl loop_control; + TEST_AND_RETURN_FALSE(loop_control.Attach(data_fd, 5s, out_lo_dev_name)); + int loop_device_fd = open(out_lo_dev_name->c_str(), O_RDWR | O_CLOEXEC); + ScopedFdCloser loop_fd_closer(&loop_device_fd); + int loop_number; + TEST_AND_RETURN_FALSE( + sscanf(out_lo_dev_name->c_str(), "/dev/block/loop%d", &loop_number) == 1); + return SetLoopDeviceStatus(loop_device_fd, filename, loop_number, writable); +#else + return BindToUnusedLoopDeviceLegacy( + data_fd, filename, writable, out_lo_dev_name); +#endif } bool UnbindLoopDevice(const string& lo_dev_name) { diff --git a/common/utils.cc b/common/utils.cc index 50b45fa0..9e1e6c58 100644 --- a/common/utils.cc +++ b/common/utils.cc @@ -30,6 +30,7 @@ #include <sys/resource.h> #include <sys/stat.h> #include <sys/types.h> +#include <time.h> #include <unistd.h> #include <algorithm> @@ -819,7 +820,7 @@ ErrorCode GetBaseErrorCode(ErrorCode code) { return base_code; } -string StringVectorToString(const vector<string> &vec_str) { +string StringVectorToString(const vector<string>& vec_str) { string str = "["; for (vector<string>::const_iterator i = vec_str.begin(); i != vec_str.end(); ++i) { @@ -848,7 +849,7 @@ string CalculateP2PFileId(const brillo::Blob& payload_hash, encoded_hash.c_str()); } -bool ConvertToOmahaInstallDate(Time time, int *out_num_days) { +bool ConvertToOmahaInstallDate(Time time, int* out_num_days) { time_t unix_time = time.ToTimeT(); // Output of: date +"%s" --date="Jan 1, 2007 0:00 PST". const time_t kOmahaEpoch = 1167638400; @@ -978,10 +979,58 @@ void ParseRollbackKeyVersion(const string& raw_version, } } +string GetFilePath(int fd) { + base::FilePath proc("/proc/self/fd/" + std::to_string(fd)); + base::FilePath file_name; + + if (!base::ReadSymbolicLink(proc, &file_name)) { + return "not found"; + } + return file_name.value(); +} + +string GetTimeAsString(time_t utime) { + struct tm tm; + CHECK_EQ(localtime_r(&utime, &tm), &tm); + char str[16]; + CHECK_EQ(strftime(str, sizeof(str), "%Y%m%d-%H%M%S", &tm), 15u); + return str; +} + string GetExclusionName(const string& str_to_convert) { return base::NumberToString(base::StringPieceHash()(str_to_convert)); } +static bool ParseTimestamp(const std::string& str, int64_t* out) { + if (!base::StringToInt64(str, out)) { + LOG(WARNING) << "Invalid timestamp: " << str; + return false; + } + return true; +} + +ErrorCode IsTimestampNewer(const std::string& old_version, + const std::string& new_version) { + if (old_version.empty() || new_version.empty()) { + LOG(WARNING) + << "One of old/new timestamp is empty, permit update anyway. Old: " + << old_version << " New: " << new_version; + return ErrorCode::kSuccess; + } + int64_t old_ver = 0; + if (!ParseTimestamp(old_version, &old_ver)) { + return ErrorCode::kError; + } + int64_t new_ver = 0; + if (!ParseTimestamp(new_version, &new_ver)) { + return ErrorCode::kDownloadManifestParseError; + } + if (old_ver > new_ver) { + return ErrorCode::kPayloadTimestampError; + } + return ErrorCode::kSuccess; +} + } // namespace utils } // namespace chromeos_update_engine diff --git a/common/utils.h b/common/utils.h index b6880ed0..bcaed318 100644 --- a/common/utils.h +++ b/common/utils.h @@ -18,6 +18,7 @@ #define UPDATE_ENGINE_COMMON_UTILS_H_ #include <errno.h> +#include <time.h> #include <unistd.h> #include <algorithm> @@ -295,6 +296,9 @@ bool GetBootId(std::string* boot_id); // shell command. Returns true on success. bool GetVpdValue(std::string key, std::string* result); +// This function gets the file path of the file pointed to by FileDiscriptor. +std::string GetFilePath(int fd); + // Divide |x| by |y| and round up to the nearest integer. constexpr uint64_t DivRoundUp(uint64_t x, uint64_t y) { return (x + y - 1) / y; @@ -317,10 +321,23 @@ void ParseRollbackKeyVersion(const std::string& raw_version, uint16_t* high_version, uint16_t* low_version); +// Return a string representation of |utime| for log file names. +std::string GetTimeAsString(time_t utime); // Returns the string format of the hashed |str_to_convert| that can be used // with |Excluder| as the exclusion name. std::string GetExclusionName(const std::string& str_to_convert); +// Parse `old_version` and `new_version` as integer timestamps and +// Return kSuccess if `new_version` is larger/newer. +// Return kSuccess if either one is empty. +// Return kError if |old_version| is not empty and not an integer. +// Return kDownloadManifestParseError if |new_version| is not empty and not an +// integer. +// Return kPayloadTimestampError if both are integers but |new_version| < +// |old_version|. +ErrorCode IsTimestampNewer(const std::string& old_version, + const std::string& new_version); + } // namespace utils // Utility class to close a file descriptor diff --git a/common/utils_unittest.cc b/common/utils_unittest.cc index f9eb5966..d73b3da7 100644 --- a/common/utils_unittest.cc +++ b/common/utils_unittest.cc @@ -472,4 +472,23 @@ TEST(UtilsTest, ParseDottedVersion) { ExpectInvalidParseRollbackKeyVersion("1.99999"); } +TEST(UtilsTest, GetFilePathTest) { + test_utils::ScopedTempFile file; + int fd = HANDLE_EINTR(open(file.path().c_str(), O_RDONLY)); + EXPECT_GE(fd, 0); + EXPECT_EQ(file.path(), utils::GetFilePath(fd)); + EXPECT_EQ("not found", utils::GetFilePath(-1)); + IGNORE_EINTR(close(fd)); +} + +TEST(UtilsTest, ValidatePerPartitionTimestamp) { + ASSERT_EQ(ErrorCode::kPayloadTimestampError, + utils::IsTimestampNewer("10", "5")); + ASSERT_EQ(ErrorCode::kSuccess, utils::IsTimestampNewer("10", "11")); + ASSERT_EQ(ErrorCode::kDownloadManifestParseError, + utils::IsTimestampNewer("10", "lol")); + ASSERT_EQ(ErrorCode::kError, utils::IsTimestampNewer("lol", "ZZZ")); + ASSERT_EQ(ErrorCode::kSuccess, utils::IsTimestampNewer("10", "")); +} + } // namespace chromeos_update_engine diff --git a/daemon_android.cc b/daemon_android.cc index 1aa921f8..313d7ddc 100644 --- a/daemon_android.cc +++ b/daemon_android.cc @@ -47,16 +47,26 @@ int DaemonAndroid::OnInit() { LOG_IF(ERROR, !daemon_state_android->Initialize()) << "Failed to initialize system state."; + auto binder_wrapper = android::BinderWrapper::Get(); + // Create the Binder Service. binder_service_ = new BinderUpdateEngineAndroidService{ daemon_state_android->service_delegate()}; - auto binder_wrapper = android::BinderWrapper::Get(); if (!binder_wrapper->RegisterService(binder_service_->ServiceName(), binder_service_)) { LOG(ERROR) << "Failed to register binder service."; } - daemon_state_->AddObserver(binder_service_.get()); + + // Create the stable binder service. + stable_binder_service_ = new BinderUpdateEngineAndroidStableService{ + daemon_state_android->service_delegate()}; + if (!binder_wrapper->RegisterService(stable_binder_service_->ServiceName(), + stable_binder_service_)) { + LOG(ERROR) << "Failed to register stable binder service."; + } + daemon_state_->AddObserver(stable_binder_service_.get()); + daemon_state_->StartUpdater(); return EX_OK; } diff --git a/daemon_android.h b/daemon_android.h index baead373..f0c028ec 100644 --- a/daemon_android.h +++ b/daemon_android.h @@ -22,6 +22,7 @@ #include <brillo/binder_watcher.h> #include "update_engine/binder_service_android.h" +#include "update_engine/binder_service_stable_android.h" #include "update_engine/common/subprocess.h" #include "update_engine/daemon_base.h" #include "update_engine/daemon_state_interface.h" @@ -43,6 +44,7 @@ class DaemonAndroid : public DaemonBase { brillo::BinderWatcher binder_watcher_; android::sp<BinderUpdateEngineAndroidService> binder_service_; + android::sp<BinderUpdateEngineAndroidStableService> stable_binder_service_; // The daemon state with all the required daemon classes for the configured // platform. diff --git a/daemon_state_android.cc b/daemon_state_android.cc index c9c09b88..3376e64a 100644 --- a/daemon_state_android.cc +++ b/daemon_state_android.cc @@ -45,17 +45,17 @@ bool DaemonStateAndroid::Initialize() { // Initialize prefs. base::FilePath non_volatile_path; - // TODO(deymo): Fall back to in-memory prefs if there's no physical directory - // available. if (!hardware_->GetNonVolatileDirectory(&non_volatile_path)) { - LOG(ERROR) << "Failed to get a non-volatile directory."; - return false; - } - Prefs* prefs = new Prefs(); - prefs_.reset(prefs); - if (!prefs->Init(non_volatile_path.Append(kPrefsSubDirectory))) { - LOG(ERROR) << "Failed to initialize preferences."; - return false; + prefs_.reset(new MemoryPrefs()); + LOG(WARNING) + << "Could not get a non-volatile directory, fall back to memory prefs"; + } else { + Prefs* prefs = new Prefs(); + prefs_.reset(prefs); + if (!prefs->Init(non_volatile_path.Append(kPrefsSubDirectory))) { + LOG(ERROR) << "Failed to initialize preferences."; + return false; + } } // The CertificateChecker singleton is used by the update attempter. diff --git a/dynamic_partition_control_android.cc b/dynamic_partition_control_android.cc index bd34ea91..ccb99ba4 100644 --- a/dynamic_partition_control_android.cc +++ b/dynamic_partition_control_android.cc @@ -16,60 +16,162 @@ #include "update_engine/dynamic_partition_control_android.h" +#include <chrono> // NOLINT(build/c++11) - using libsnapshot / liblp API +#include <map> #include <memory> #include <set> #include <string> +#include <string_view> +#include <utility> +#include <vector> #include <android-base/properties.h> #include <android-base/strings.h> #include <base/files/file_util.h> #include <base/logging.h> +#include <base/strings/string_util.h> #include <bootloader_message/bootloader_message.h> +#include <fs_mgr.h> #include <fs_mgr_dm_linear.h> +#include <fs_mgr_overlayfs.h> +#include <libavb/libavb.h> +#include <libdm/dm.h> +#include <libsnapshot/snapshot.h> +#include <libsnapshot/snapshot_stub.h> +#include "update_engine/cleanup_previous_update_action.h" #include "update_engine/common/boot_control_interface.h" #include "update_engine/common/utils.h" +#include "update_engine/dynamic_partition_utils.h" +#include "update_engine/payload_consumer/delta_performer.h" using android::base::GetBoolProperty; +using android::base::GetProperty; using android::base::Join; using android::dm::DeviceMapper; using android::dm::DmDeviceState; using android::fs_mgr::CreateLogicalPartition; +using android::fs_mgr::CreateLogicalPartitionParams; using android::fs_mgr::DestroyLogicalPartition; +using android::fs_mgr::Fstab; using android::fs_mgr::MetadataBuilder; +using android::fs_mgr::Partition; using android::fs_mgr::PartitionOpener; +using android::fs_mgr::SlotSuffixForSlotNumber; +using android::snapshot::OptimizeSourceCopyOperation; +using android::snapshot::Return; +using android::snapshot::SnapshotManager; +using android::snapshot::SnapshotManagerStub; +using android::snapshot::UpdateState; namespace chromeos_update_engine { constexpr char kUseDynamicPartitions[] = "ro.boot.dynamic_partitions"; constexpr char kRetrfoitDynamicPartitions[] = "ro.boot.dynamic_partitions_retrofit"; -constexpr uint64_t kMapTimeoutMillis = 1000; +constexpr char kVirtualAbEnabled[] = "ro.virtual_ab.enabled"; +constexpr char kVirtualAbRetrofit[] = "ro.virtual_ab.retrofit"; +constexpr char kPostinstallFstabPrefix[] = "ro.postinstall.fstab.prefix"; +// Map timeout for dynamic partitions. +constexpr std::chrono::milliseconds kMapTimeout{1000}; +// Map timeout for dynamic partitions with snapshots. Since several devices +// needs to be mapped, this timeout is longer than |kMapTimeout|. +constexpr std::chrono::milliseconds kMapSnapshotTimeout{5000}; + +#ifdef __ANDROID_RECOVERY__ +constexpr bool kIsRecovery = true; +#else +constexpr bool kIsRecovery = false; +#endif DynamicPartitionControlAndroid::~DynamicPartitionControlAndroid() { - CleanupInternal(false /* wait */); + Cleanup(); +} + +static FeatureFlag GetFeatureFlag(const char* enable_prop, + const char* retrofit_prop) { + bool retrofit = GetBoolProperty(retrofit_prop, false); + bool enabled = GetBoolProperty(enable_prop, false); + if (retrofit && !enabled) { + LOG(ERROR) << retrofit_prop << " is true but " << enable_prop + << " is not. These sysprops are inconsistent. Assume that " + << enable_prop << " is true from now on."; + } + if (retrofit) { + return FeatureFlag(FeatureFlag::Value::RETROFIT); + } + if (enabled) { + return FeatureFlag(FeatureFlag::Value::LAUNCH); + } + return FeatureFlag(FeatureFlag::Value::NONE); } -bool DynamicPartitionControlAndroid::IsDynamicPartitionsEnabled() { - return GetBoolProperty(kUseDynamicPartitions, false); +DynamicPartitionControlAndroid::DynamicPartitionControlAndroid() + : dynamic_partitions_( + GetFeatureFlag(kUseDynamicPartitions, kRetrfoitDynamicPartitions)), + virtual_ab_(GetFeatureFlag(kVirtualAbEnabled, kVirtualAbRetrofit)) { + if (GetVirtualAbFeatureFlag().IsEnabled()) { + snapshot_ = SnapshotManager::New(); + } else { + snapshot_ = SnapshotManagerStub::New(); + } + CHECK(snapshot_ != nullptr) << "Cannot initialize SnapshotManager."; } -bool DynamicPartitionControlAndroid::IsDynamicPartitionsRetrofit() { - return GetBoolProperty(kRetrfoitDynamicPartitions, false); +FeatureFlag DynamicPartitionControlAndroid::GetDynamicPartitionsFeatureFlag() { + return dynamic_partitions_; } -bool DynamicPartitionControlAndroid::MapPartitionOnDeviceMapper( +FeatureFlag DynamicPartitionControlAndroid::GetVirtualAbFeatureFlag() { + return virtual_ab_; +} + +bool DynamicPartitionControlAndroid::OptimizeOperation( + const std::string& partition_name, + const InstallOperation& operation, + InstallOperation* optimized) { + switch (operation.type()) { + case InstallOperation::SOURCE_COPY: + return target_supports_snapshot_ && + GetVirtualAbFeatureFlag().IsEnabled() && + mapped_devices_.count(partition_name + + SlotSuffixForSlotNumber(target_slot_)) > 0 && + OptimizeSourceCopyOperation(operation, optimized); + break; + default: + break; + } + return false; +} + +bool DynamicPartitionControlAndroid::MapPartitionInternal( const std::string& super_device, const std::string& target_partition_name, uint32_t slot, bool force_writable, std::string* path) { - if (!CreateLogicalPartition(super_device.c_str(), - slot, - target_partition_name, - force_writable, - std::chrono::milliseconds(kMapTimeoutMillis), - path)) { + CreateLogicalPartitionParams params = { + .block_device = super_device, + .metadata_slot = slot, + .partition_name = target_partition_name, + .force_writable = force_writable, + }; + bool success = false; + if (GetVirtualAbFeatureFlag().IsEnabled() && target_supports_snapshot_ && + force_writable && ExpectMetadataMounted()) { + // Only target partitions are mapped with force_writable. On Virtual + // A/B devices, target partitions may overlap with source partitions, so + // they must be mapped with snapshot. + // One exception is when /metadata is not mounted. Fallback to + // CreateLogicalPartition as snapshots are not created in the first place. + params.timeout_ms = kMapSnapshotTimeout; + success = snapshot_->MapUpdateSnapshot(params, path); + } else { + params.timeout_ms = kMapTimeout; + success = CreateLogicalPartition(params, path); + } + + if (!success) { LOG(ERROR) << "Cannot map " << target_partition_name << " in " << super_device << " on device mapper."; return false; @@ -81,13 +183,71 @@ bool DynamicPartitionControlAndroid::MapPartitionOnDeviceMapper( return true; } +bool DynamicPartitionControlAndroid::MapPartitionOnDeviceMapper( + const std::string& super_device, + const std::string& target_partition_name, + uint32_t slot, + bool force_writable, + std::string* path) { + DmDeviceState state = GetState(target_partition_name); + if (state == DmDeviceState::ACTIVE) { + if (mapped_devices_.find(target_partition_name) != mapped_devices_.end()) { + if (GetDmDevicePathByName(target_partition_name, path)) { + LOG(INFO) << target_partition_name + << " is mapped on device mapper: " << *path; + return true; + } + LOG(ERROR) << target_partition_name << " is mapped but path is unknown."; + return false; + } + // If target_partition_name is not in mapped_devices_ but state is ACTIVE, + // the device might be mapped incorrectly before. Attempt to unmap it. + // Note that for source partitions, if GetState() == ACTIVE, callers (e.g. + // BootControlAndroid) should not call MapPartitionOnDeviceMapper, but + // should directly call GetDmDevicePathByName. + if (!UnmapPartitionOnDeviceMapper(target_partition_name)) { + LOG(ERROR) << target_partition_name + << " is mapped before the update, and it cannot be unmapped."; + return false; + } + state = GetState(target_partition_name); + if (state != DmDeviceState::INVALID) { + LOG(ERROR) << target_partition_name << " is unmapped but state is " + << static_cast<std::underlying_type_t<DmDeviceState>>(state); + return false; + } + } + if (state == DmDeviceState::INVALID) { + return MapPartitionInternal( + super_device, target_partition_name, slot, force_writable, path); + } + + LOG(ERROR) << target_partition_name + << " is mapped on device mapper but state is unknown: " + << static_cast<std::underlying_type_t<DmDeviceState>>(state); + return false; +} + bool DynamicPartitionControlAndroid::UnmapPartitionOnDeviceMapper( - const std::string& target_partition_name, bool wait) { + const std::string& target_partition_name) { if (DeviceMapper::Instance().GetState(target_partition_name) != DmDeviceState::INVALID) { - if (!DestroyLogicalPartition( - target_partition_name, - std::chrono::milliseconds(wait ? kMapTimeoutMillis : 0))) { + // Partitions at target slot on non-Virtual A/B devices are mapped as + // dm-linear. Also, on Virtual A/B devices, system_other may be mapped for + // preopt apps as dm-linear. + // Call DestroyLogicalPartition to handle these cases. + bool success = DestroyLogicalPartition(target_partition_name); + + // On a Virtual A/B device, |target_partition_name| may be a leftover from + // a paused update. Clean up any underlying devices. + if (ExpectMetadataMounted()) { + success &= snapshot_->UnmapUpdateSnapshot(target_partition_name); + } else { + LOG(INFO) << "Skip UnmapUpdateSnapshot(" << target_partition_name + << ") because metadata is not mounted"; + } + + if (!success) { LOG(ERROR) << "Cannot unmap " << target_partition_name << " from device mapper."; return false; @@ -99,18 +259,22 @@ bool DynamicPartitionControlAndroid::UnmapPartitionOnDeviceMapper( return true; } -void DynamicPartitionControlAndroid::CleanupInternal(bool wait) { +void DynamicPartitionControlAndroid::UnmapAllPartitions() { + if (mapped_devices_.empty()) { + return; + } // UnmapPartitionOnDeviceMapper removes objects from mapped_devices_, hence // a copy is needed for the loop. std::set<std::string> mapped = mapped_devices_; LOG(INFO) << "Destroying [" << Join(mapped, ", ") << "] from device mapper"; for (const auto& partition_name : mapped) { - ignore_result(UnmapPartitionOnDeviceMapper(partition_name, wait)); + ignore_result(UnmapPartitionOnDeviceMapper(partition_name)); } } void DynamicPartitionControlAndroid::Cleanup() { - CleanupInternal(true /* wait */); + UnmapAllPartitions(); + metadata_device_.reset(); } bool DynamicPartitionControlAndroid::DeviceExists(const std::string& path) { @@ -129,27 +293,36 @@ bool DynamicPartitionControlAndroid::GetDmDevicePathByName( std::unique_ptr<MetadataBuilder> DynamicPartitionControlAndroid::LoadMetadataBuilder( + const std::string& super_device, uint32_t slot) { + auto builder = MetadataBuilder::New(PartitionOpener(), super_device, slot); + if (builder == nullptr) { + LOG(WARNING) << "No metadata slot " << BootControlInterface::SlotName(slot) + << " in " << super_device; + return nullptr; + } + LOG(INFO) << "Loaded metadata from slot " + << BootControlInterface::SlotName(slot) << " in " << super_device; + return builder; +} + +std::unique_ptr<MetadataBuilder> +DynamicPartitionControlAndroid::LoadMetadataBuilder( const std::string& super_device, uint32_t source_slot, uint32_t target_slot) { - std::unique_ptr<MetadataBuilder> builder; - - if (target_slot != BootControlInterface::kInvalidSlot && - IsDynamicPartitionsRetrofit()) { - builder = MetadataBuilder::NewForUpdate( - PartitionOpener(), super_device, source_slot, target_slot); - } else { - builder = - MetadataBuilder::New(PartitionOpener(), super_device, source_slot); - } - + bool always_keep_source_slot = !target_supports_snapshot_; + auto builder = MetadataBuilder::NewForUpdate(PartitionOpener(), + super_device, + source_slot, + target_slot, + always_keep_source_slot); if (builder == nullptr) { LOG(WARNING) << "No metadata slot " << BootControlInterface::SlotName(source_slot) << " in " << super_device; return nullptr; } - LOG(INFO) << "Loaded metadata from slot " + LOG(INFO) << "Created metadata for new update from slot " << BootControlInterface::SlotName(source_slot) << " in " << super_device; return builder; @@ -167,7 +340,7 @@ bool DynamicPartitionControlAndroid::StoreMetadata( return false; } - if (IsDynamicPartitionsRetrofit()) { + if (GetDynamicPartitionsFeatureFlag().IsRetrofit()) { if (!FlashPartitionTable(super_device, *metadata)) { LOG(ERROR) << "Cannot write metadata to " << super_device; return false; @@ -222,4 +395,828 @@ bool DynamicPartitionControlAndroid::GetDeviceDir(std::string* out) { *out = base::FilePath(misc_device).DirName().value(); return true; } + +bool DynamicPartitionControlAndroid::PreparePartitionsForUpdate( + uint32_t source_slot, + uint32_t target_slot, + const DeltaArchiveManifest& manifest, + bool update, + uint64_t* required_size) { + source_slot_ = source_slot; + target_slot_ = target_slot; + if (required_size != nullptr) { + *required_size = 0; + } + + if (fs_mgr_overlayfs_is_setup()) { + // Non DAP devices can use overlayfs as well. + LOG(WARNING) + << "overlayfs overrides are active and can interfere with our " + "resources.\n" + << "run adb enable-verity to deactivate if required and try again."; + } + + // If metadata is erased but not formatted, it is possible to not mount + // it in recovery. It is acceptable to skip mounting and choose fallback path + // (PrepareDynamicPartitionsForUpdate) when sideloading full OTAs. + TEST_AND_RETURN_FALSE(EnsureMetadataMounted() || IsRecovery()); + + if (update) { + TEST_AND_RETURN_FALSE(EraseSystemOtherAvbFooter(source_slot, target_slot)); + } + + if (!GetDynamicPartitionsFeatureFlag().IsEnabled()) { + return true; + } + + if (target_slot == source_slot) { + LOG(ERROR) << "Cannot call PreparePartitionsForUpdate on current slot."; + return false; + } + + if (!SetTargetBuildVars(manifest)) { + return false; + } + + // Although the current build supports dynamic partitions, the given payload + // doesn't use it for target partitions. This could happen when applying a + // retrofit update. Skip updating the partition metadata for the target slot. + if (!is_target_dynamic_) { + return true; + } + + if (!update) + return true; + + bool delete_source = false; + + if (GetVirtualAbFeatureFlag().IsEnabled()) { + // On Virtual A/B device, either CancelUpdate() or BeginUpdate() must be + // called before calling UnmapUpdateSnapshot. + // - If target_supports_snapshot_, PrepareSnapshotPartitionsForUpdate() + // calls BeginUpdate() which resets update state + // - If !target_supports_snapshot_ or PrepareSnapshotPartitionsForUpdate + // failed in recovery, explicitly CancelUpdate(). + if (target_supports_snapshot_) { + if (PrepareSnapshotPartitionsForUpdate( + source_slot, target_slot, manifest, required_size)) { + return true; + } + + // Virtual A/B device doing Virtual A/B update in Android mode must use + // snapshots. + if (!IsRecovery()) { + LOG(ERROR) << "PrepareSnapshotPartitionsForUpdate failed in Android " + << "mode"; + return false; + } + + delete_source = true; + LOG(INFO) << "PrepareSnapshotPartitionsForUpdate failed in recovery. " + << "Attempt to overwrite existing partitions if possible"; + } else { + // Downgrading to an non-Virtual A/B build or is secondary OTA. + LOG(INFO) << "Using regular A/B on Virtual A/B because package disabled " + << "snapshots."; + } + + // In recovery, if /metadata is not mounted, it is likely that metadata + // partition is erased and not formatted yet. After sideloading, when + // rebooting into the new version, init will erase metadata partition, + // hence the failure of CancelUpdate() can be ignored here. + // However, if metadata is mounted and CancelUpdate fails, sideloading + // should not proceed because during next boot, snapshots will overlay on + // the devices incorrectly. + if (ExpectMetadataMounted()) { + TEST_AND_RETURN_FALSE(snapshot_->CancelUpdate()); + } else { + LOG(INFO) << "Skip canceling previous update because metadata is not " + << "mounted"; + } + } + + // TODO(xunchang) support partial update on non VAB enabled devices. + TEST_AND_RETURN_FALSE(PrepareDynamicPartitionsForUpdate( + source_slot, target_slot, manifest, delete_source)); + + if (required_size != nullptr) { + *required_size = 0; + } + return true; +} + +bool DynamicPartitionControlAndroid::SetTargetBuildVars( + const DeltaArchiveManifest& manifest) { + // Precondition: current build supports dynamic partition. + CHECK(GetDynamicPartitionsFeatureFlag().IsEnabled()); + + bool is_target_dynamic = + !manifest.dynamic_partition_metadata().groups().empty(); + bool target_supports_snapshot = + manifest.dynamic_partition_metadata().snapshot_enabled(); + + if (manifest.partial_update()) { + // Partial updates requires DAP. On partial updates that does not involve + // dynamic partitions, groups() can be empty, so also assume + // is_target_dynamic in this case. This assumption should be safe because we + // also check target_supports_snapshot below, which presumably also implies + // target build supports dynamic partition. + if (!is_target_dynamic) { + LOG(INFO) << "Assuming target build supports dynamic partitions for " + "partial updates."; + is_target_dynamic = true; + } + + // Partial updates requires Virtual A/B. Double check that both current + // build and target build supports Virtual A/B. + if (!GetVirtualAbFeatureFlag().IsEnabled()) { + LOG(ERROR) << "Partial update cannot be applied on a device that does " + "not support snapshots."; + return false; + } + if (!target_supports_snapshot) { + LOG(ERROR) << "Cannot apply partial update to a build that does not " + "support snapshots."; + return false; + } + } + + // Store the flags. + is_target_dynamic_ = is_target_dynamic; + // If !is_target_dynamic_, leave target_supports_snapshot_ unset because + // snapshots would not work without dynamic partition. + if (is_target_dynamic_) { + target_supports_snapshot_ = target_supports_snapshot; + } + return true; +} + +namespace { +// Try our best to erase AVB footer. +class AvbFooterEraser { + public: + explicit AvbFooterEraser(const std::string& path) : path_(path) {} + bool Erase() { + // Try to mark the block device read-only. Ignore any + // failure since this won't work when passing regular files. + ignore_result(utils::SetBlockDeviceReadOnly(path_, false /* readonly */)); + + fd_.reset(new EintrSafeFileDescriptor()); + int flags = O_WRONLY | O_TRUNC | O_CLOEXEC | O_SYNC; + TEST_AND_RETURN_FALSE(fd_->Open(path_.c_str(), flags)); + + // Need to write end-AVB_FOOTER_SIZE to end. + static_assert(AVB_FOOTER_SIZE > 0); + off64_t offset = fd_->Seek(-AVB_FOOTER_SIZE, SEEK_END); + TEST_AND_RETURN_FALSE_ERRNO(offset >= 0); + uint64_t write_size = AVB_FOOTER_SIZE; + LOG(INFO) << "Zeroing " << path_ << " @ [" << offset << ", " + << (offset + write_size) << "] (" << write_size << " bytes)"; + brillo::Blob zeros(write_size); + TEST_AND_RETURN_FALSE(utils::WriteAll(fd_, zeros.data(), zeros.size())); + return true; + } + ~AvbFooterEraser() { + TEST_AND_RETURN(fd_ != nullptr && fd_->IsOpen()); + if (!fd_->Close()) { + LOG(WARNING) << "Failed to close fd for " << path_; + } + } + + private: + std::string path_; + FileDescriptorPtr fd_; +}; + +} // namespace + +std::optional<bool> +DynamicPartitionControlAndroid::IsAvbEnabledOnSystemOther() { + auto prefix = GetProperty(kPostinstallFstabPrefix, ""); + if (prefix.empty()) { + LOG(WARNING) << "Cannot get " << kPostinstallFstabPrefix; + return std::nullopt; + } + auto path = base::FilePath(prefix).Append("etc/fstab.postinstall").value(); + return IsAvbEnabledInFstab(path); +} + +std::optional<bool> DynamicPartitionControlAndroid::IsAvbEnabledInFstab( + const std::string& path) { + Fstab fstab; + if (!ReadFstabFromFile(path, &fstab)) { + PLOG(WARNING) << "Cannot read fstab from " << path; + if (errno == ENOENT) { + return false; + } + return std::nullopt; + } + for (const auto& entry : fstab) { + if (!entry.avb_keys.empty()) { + return true; + } + } + return false; +} + +bool DynamicPartitionControlAndroid::GetSystemOtherPath( + uint32_t source_slot, + uint32_t target_slot, + const std::string& partition_name_suffix, + std::string* path, + bool* should_unmap) { + path->clear(); + *should_unmap = false; + + // Check that AVB is enabled on system_other before erasing. + auto has_avb = IsAvbEnabledOnSystemOther(); + TEST_AND_RETURN_FALSE(has_avb.has_value()); + if (!has_avb.value()) { + LOG(INFO) << "AVB is not enabled on system_other. Skip erasing."; + return true; + } + + if (!IsRecovery()) { + // Found unexpected avb_keys for system_other on devices retrofitting + // dynamic partitions. Previous crash in update_engine may leave logical + // partitions mapped on physical system_other partition. It is difficult to + // handle these cases. Just fail. + if (GetDynamicPartitionsFeatureFlag().IsRetrofit()) { + LOG(ERROR) << "Cannot erase AVB footer on system_other on devices with " + << "retrofit dynamic partitions. They should not have AVB " + << "enabled on system_other."; + return false; + } + } + + std::string device_dir_str; + TEST_AND_RETURN_FALSE(GetDeviceDir(&device_dir_str)); + base::FilePath device_dir(device_dir_str); + + // On devices without dynamic partition, search for static partitions. + if (!GetDynamicPartitionsFeatureFlag().IsEnabled()) { + *path = device_dir.Append(partition_name_suffix).value(); + TEST_AND_RETURN_FALSE(DeviceExists(*path)); + return true; + } + + auto source_super_device = + device_dir.Append(GetSuperPartitionName(source_slot)).value(); + + auto builder = LoadMetadataBuilder(source_super_device, source_slot); + if (builder == nullptr) { + if (IsRecovery()) { + // It might be corrupted for some reason. It should still be able to + // sideload. + LOG(WARNING) << "Super partition metadata cannot be read from the source " + << "slot, skip erasing."; + return true; + } else { + // Device has booted into Android mode, indicating that the super + // partition metadata should be there. + LOG(ERROR) << "Super partition metadata cannot be read from the source " + << "slot. This is unexpected on devices with dynamic " + << "partitions enabled."; + return false; + } + } + auto p = builder->FindPartition(partition_name_suffix); + if (p == nullptr) { + // If the source slot is flashed without system_other, it does not exist + // in super partition metadata at source slot. It is safe to skip it. + LOG(INFO) << "Can't find " << partition_name_suffix + << " in metadata source slot, skip erasing."; + return true; + } + // System_other created by flashing tools should be erased. + // If partition is created by update_engine (via NewForUpdate), it is a + // left-over partition from the previous update and does not contain + // system_other, hence there is no need to erase. + // Note the reverse is not necessary true. If the flag is not set, we don't + // know if the partition is created by update_engine or by flashing tools + // because older versions of super partition metadata does not contain this + // flag. It is okay to erase the AVB footer anyways. + if (p->attributes() & LP_PARTITION_ATTR_UPDATED) { + LOG(INFO) << partition_name_suffix + << " does not contain system_other, skip erasing."; + return true; + } + + if (p->size() < AVB_FOOTER_SIZE) { + LOG(INFO) << partition_name_suffix << " has length " << p->size() + << "( < AVB_FOOTER_SIZE " << AVB_FOOTER_SIZE + << "), skip erasing."; + return true; + } + + // Delete any pre-existing device with name |partition_name_suffix| and + // also remove it from |mapped_devices_|. + // In recovery, metadata might not be mounted, and + // UnmapPartitionOnDeviceMapper might fail. However, + // it is unusual that system_other has already been mapped. Hence, just skip. + TEST_AND_RETURN_FALSE(UnmapPartitionOnDeviceMapper(partition_name_suffix)); + // Use CreateLogicalPartition directly to avoid mapping with existing + // snapshots. + CreateLogicalPartitionParams params = { + .block_device = source_super_device, + .metadata_slot = source_slot, + .partition_name = partition_name_suffix, + .force_writable = true, + .timeout_ms = kMapTimeout, + }; + TEST_AND_RETURN_FALSE(CreateLogicalPartition(params, path)); + *should_unmap = true; + return true; +} + +bool DynamicPartitionControlAndroid::EraseSystemOtherAvbFooter( + uint32_t source_slot, uint32_t target_slot) { + LOG(INFO) << "Erasing AVB footer of system_other partition before update."; + + const std::string target_suffix = SlotSuffixForSlotNumber(target_slot); + const std::string partition_name_suffix = "system" + target_suffix; + + std::string path; + bool should_unmap = false; + + TEST_AND_RETURN_FALSE(GetSystemOtherPath( + source_slot, target_slot, partition_name_suffix, &path, &should_unmap)); + + if (path.empty()) { + return true; + } + + bool ret = AvbFooterEraser(path).Erase(); + + // Delete |partition_name_suffix| from device mapper and from + // |mapped_devices_| again so that it does not interfere with update process. + // In recovery, metadata might not be mounted, and + // UnmapPartitionOnDeviceMapper might fail. However, DestroyLogicalPartition + // should be called. If DestroyLogicalPartition does fail, it is still okay + // to skip the error here and let Prepare*() fail later. + if (should_unmap) { + TEST_AND_RETURN_FALSE(UnmapPartitionOnDeviceMapper(partition_name_suffix)); + } + + return ret; +} + +bool DynamicPartitionControlAndroid::PrepareDynamicPartitionsForUpdate( + uint32_t source_slot, + uint32_t target_slot, + const DeltaArchiveManifest& manifest, + bool delete_source) { + const std::string target_suffix = SlotSuffixForSlotNumber(target_slot); + + // Unmap all the target dynamic partitions because they would become + // inconsistent with the new metadata. + for (const auto& group : manifest.dynamic_partition_metadata().groups()) { + for (const auto& partition_name : group.partition_names()) { + if (!UnmapPartitionOnDeviceMapper(partition_name + target_suffix)) { + return false; + } + } + } + + std::string device_dir_str; + if (!GetDeviceDir(&device_dir_str)) { + return false; + } + base::FilePath device_dir(device_dir_str); + auto source_device = + device_dir.Append(GetSuperPartitionName(source_slot)).value(); + + auto builder = LoadMetadataBuilder(source_device, source_slot, target_slot); + if (builder == nullptr) { + LOG(ERROR) << "No metadata at " + << BootControlInterface::SlotName(source_slot); + return false; + } + + if (delete_source) { + TEST_AND_RETURN_FALSE( + DeleteSourcePartitions(builder.get(), source_slot, manifest)); + } + + if (!UpdatePartitionMetadata(builder.get(), target_slot, manifest)) { + return false; + } + + auto target_device = + device_dir.Append(GetSuperPartitionName(target_slot)).value(); + return StoreMetadata(target_device, builder.get(), target_slot); +} + +bool DynamicPartitionControlAndroid::PrepareSnapshotPartitionsForUpdate( + uint32_t source_slot, + uint32_t target_slot, + const DeltaArchiveManifest& manifest, + uint64_t* required_size) { + TEST_AND_RETURN_FALSE(ExpectMetadataMounted()); + if (!snapshot_->BeginUpdate()) { + LOG(ERROR) << "Cannot begin new update."; + return false; + } + auto ret = snapshot_->CreateUpdateSnapshots(manifest); + if (!ret) { + LOG(ERROR) << "Cannot create update snapshots: " << ret.string(); + if (required_size != nullptr && + ret.error_code() == Return::ErrorCode::NO_SPACE) { + *required_size = ret.required_size(); + } + return false; + } + return true; +} + +std::string DynamicPartitionControlAndroid::GetSuperPartitionName( + uint32_t slot) { + return fs_mgr_get_super_partition_name(slot); +} + +bool DynamicPartitionControlAndroid::UpdatePartitionMetadata( + MetadataBuilder* builder, + uint32_t target_slot, + const DeltaArchiveManifest& manifest) { + // Check preconditions. + CHECK(!GetVirtualAbFeatureFlag().IsEnabled() || IsRecovery()) + << "UpdatePartitionMetadata is called on a Virtual A/B device " + "but source partitions is not deleted. This is not allowed."; + + // 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 + // target partitions. + builder->RemoveGroupAndPartitions(android::snapshot::kCowGroupName); + + const std::string target_suffix = SlotSuffixForSlotNumber(target_slot); + DeleteGroupsWithSuffix(builder, target_suffix); + + uint64_t total_size = 0; + for (const auto& group : manifest.dynamic_partition_metadata().groups()) { + total_size += 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. + if (!GetDynamicPartitionsFeatureFlag().IsRetrofit() && + !GetVirtualAbFeatureFlag().IsEnabled()) { + allocatable_space /= 2; + expr = "half of "; + } + if (total_size > allocatable_space) { + LOG(ERROR) << "The maximum size of all groups with suffix " << target_suffix + << " (" << total_size << ") has exceeded " << expr + << "allocatable space for dynamic partitions " + << allocatable_space << "."; + return false; + } + + // name of partition(e.g. "system") -> size in bytes + std::map<std::string, uint64_t> partition_sizes; + for (const auto& partition : manifest.partitions()) { + partition_sizes.emplace(partition.partition_name(), + partition.new_partition_info().size()); + } + + for (const auto& group : manifest.dynamic_partition_metadata().groups()) { + auto group_name_suffix = group.name() + target_suffix; + if (!builder->AddGroup(group_name_suffix, group.size())) { + LOG(ERROR) << "Cannot add group " << group_name_suffix << " with size " + << group.size(); + return false; + } + LOG(INFO) << "Added group " << group_name_suffix << " with size " + << group.size(); + + for (const auto& partition_name : group.partition_names()) { + auto partition_sizes_it = partition_sizes.find(partition_name); + if (partition_sizes_it == partition_sizes.end()) { + // TODO(tbao): Support auto-filling partition info for framework-only + // OTA. + LOG(ERROR) << "dynamic_partition_metadata contains partition " + << partition_name << " but it is not part of the manifest. " + << "This is not supported."; + return false; + } + uint64_t partition_size = partition_sizes_it->second; + + auto partition_name_suffix = partition_name + target_suffix; + Partition* p = builder->AddPartition( + partition_name_suffix, group_name_suffix, LP_PARTITION_ATTR_READONLY); + if (!p) { + LOG(ERROR) << "Cannot add partition " << partition_name_suffix + << " to group " << group_name_suffix; + return false; + } + if (!builder->ResizePartition(p, partition_size)) { + LOG(ERROR) << "Cannot resize partition " << partition_name_suffix + << " to size " << partition_size << ". Not enough space?"; + return false; + } + LOG(INFO) << "Added partition " << partition_name_suffix << " to group " + << group_name_suffix << " with size " << partition_size; + } + } + + return true; +} + +bool DynamicPartitionControlAndroid::FinishUpdate(bool powerwash_required) { + if (ExpectMetadataMounted()) { + if (snapshot_->GetUpdateState() == UpdateState::Initiated) { + LOG(INFO) << "Snapshot writes are done."; + return snapshot_->FinishedSnapshotWrites(powerwash_required); + } + } else { + LOG(INFO) << "Skip FinishedSnapshotWrites() because /metadata is not " + << "mounted"; + } + return true; +} + +bool DynamicPartitionControlAndroid::GetPartitionDevice( + const std::string& partition_name, + uint32_t slot, + uint32_t current_slot, + bool not_in_payload, + std::string* device, + bool* is_dynamic) { + const auto& partition_name_suffix = + partition_name + SlotSuffixForSlotNumber(slot); + std::string device_dir_str; + TEST_AND_RETURN_FALSE(GetDeviceDir(&device_dir_str)); + base::FilePath device_dir(device_dir_str); + + if (is_dynamic) { + *is_dynamic = false; + } + + // When looking up target partition devices, treat them as static if the + // current payload doesn't encode them as dynamic partitions. This may happen + // when applying a retrofit update on top of a dynamic-partitions-enabled + // build. + if (GetDynamicPartitionsFeatureFlag().IsEnabled() && + (slot == current_slot || is_target_dynamic_)) { + switch (GetDynamicPartitionDevice(device_dir, + partition_name_suffix, + slot, + current_slot, + not_in_payload, + device)) { + case DynamicPartitionDeviceStatus::SUCCESS: + if (is_dynamic) { + *is_dynamic = true; + } + return true; + case DynamicPartitionDeviceStatus::TRY_STATIC: + break; + case DynamicPartitionDeviceStatus::ERROR: // fallthrough + default: + return false; + } + } + base::FilePath path = device_dir.Append(partition_name_suffix); + if (!DeviceExists(path.value())) { + LOG(ERROR) << "Device file " << path.value() << " does not exist."; + return false; + } + + *device = path.value(); + return true; +} + +bool DynamicPartitionControlAndroid::GetPartitionDevice( + const std::string& partition_name, + uint32_t slot, + uint32_t current_slot, + std::string* device) { + return GetPartitionDevice( + partition_name, slot, current_slot, false, device, nullptr); +} + +bool DynamicPartitionControlAndroid::IsSuperBlockDevice( + const base::FilePath& device_dir, + uint32_t current_slot, + const std::string& partition_name_suffix) { + std::string source_device = + device_dir.Append(GetSuperPartitionName(current_slot)).value(); + auto source_metadata = LoadMetadataBuilder(source_device, current_slot); + return source_metadata->HasBlockDevice(partition_name_suffix); +} + +DynamicPartitionControlAndroid::DynamicPartitionDeviceStatus +DynamicPartitionControlAndroid::GetDynamicPartitionDevice( + const base::FilePath& device_dir, + const std::string& partition_name_suffix, + uint32_t slot, + uint32_t current_slot, + bool not_in_payload, + std::string* device) { + std::string super_device = + device_dir.Append(GetSuperPartitionName(slot)).value(); + + auto builder = LoadMetadataBuilder(super_device, slot); + if (builder == nullptr) { + LOG(ERROR) << "No metadata in slot " + << BootControlInterface::SlotName(slot); + return DynamicPartitionDeviceStatus::ERROR; + } + if (builder->FindPartition(partition_name_suffix) == nullptr) { + LOG(INFO) << partition_name_suffix + << " is not in super partition metadata."; + + if (IsSuperBlockDevice(device_dir, current_slot, partition_name_suffix)) { + LOG(ERROR) << "The static partition " << partition_name_suffix + << " is a block device for current metadata." + << "It cannot be used as a logical partition."; + return DynamicPartitionDeviceStatus::ERROR; + } + + return DynamicPartitionDeviceStatus::TRY_STATIC; + } + + if (slot == current_slot) { + if (GetState(partition_name_suffix) != DmDeviceState::ACTIVE) { + LOG(WARNING) << partition_name_suffix << " is at current slot but it is " + << "not mapped. Now try to map it."; + } else { + if (GetDmDevicePathByName(partition_name_suffix, device)) { + LOG(INFO) << partition_name_suffix + << " is mapped on device mapper: " << *device; + return DynamicPartitionDeviceStatus::SUCCESS; + } + LOG(ERROR) << partition_name_suffix << "is mapped but path is unknown."; + return DynamicPartitionDeviceStatus::ERROR; + } + } + + bool force_writable = (slot != current_slot) && !not_in_payload; + if (MapPartitionOnDeviceMapper( + super_device, partition_name_suffix, slot, force_writable, device)) { + return DynamicPartitionDeviceStatus::SUCCESS; + } + return DynamicPartitionDeviceStatus::ERROR; +} + +void DynamicPartitionControlAndroid::set_fake_mapped_devices( + const std::set<std::string>& fake) { + mapped_devices_ = fake; +} + +bool DynamicPartitionControlAndroid::IsRecovery() { + return kIsRecovery; +} + +static bool IsIncrementalUpdate(const DeltaArchiveManifest& manifest) { + const auto& partitions = manifest.partitions(); + return std::any_of(partitions.begin(), partitions.end(), [](const auto& p) { + return p.has_old_partition_info(); + }); +} + +bool DynamicPartitionControlAndroid::DeleteSourcePartitions( + MetadataBuilder* builder, + uint32_t source_slot, + const DeltaArchiveManifest& manifest) { + TEST_AND_RETURN_FALSE(IsRecovery()); + + if (IsIncrementalUpdate(manifest)) { + LOG(ERROR) << "Cannot sideload incremental OTA because snapshots cannot " + << "be created."; + if (GetVirtualAbFeatureFlag().IsLaunch()) { + LOG(ERROR) << "Sideloading incremental updates on devices launches " + << " Virtual A/B is not supported."; + } + return false; + } + + LOG(INFO) << "Will overwrite existing partitions. Slot " + << BootControlInterface::SlotName(source_slot) + << "may be unbootable until update finishes!"; + const std::string source_suffix = SlotSuffixForSlotNumber(source_slot); + DeleteGroupsWithSuffix(builder, source_suffix); + + return true; +} + +std::unique_ptr<AbstractAction> +DynamicPartitionControlAndroid::GetCleanupPreviousUpdateAction( + BootControlInterface* boot_control, + PrefsInterface* prefs, + CleanupPreviousUpdateActionDelegateInterface* delegate) { + if (!GetVirtualAbFeatureFlag().IsEnabled()) { + return std::make_unique<NoOpAction>(); + } + return std::make_unique<CleanupPreviousUpdateAction>( + prefs, boot_control, snapshot_.get(), delegate); +} + +bool DynamicPartitionControlAndroid::ResetUpdate(PrefsInterface* prefs) { + if (!GetVirtualAbFeatureFlag().IsEnabled()) { + return true; + } + + LOG(INFO) << __func__ << " resetting update state and deleting snapshots."; + TEST_AND_RETURN_FALSE(prefs != nullptr); + + // If the device has already booted into the target slot, + // ResetUpdateProgress may pass but CancelUpdate fails. + // This is expected. A scheduled CleanupPreviousUpdateAction should free + // space when it is done. + TEST_AND_RETURN_FALSE(DeltaPerformer::ResetUpdateProgress( + prefs, false /* quick */, false /* skip dynamic partitions metadata */)); + + if (ExpectMetadataMounted()) { + TEST_AND_RETURN_FALSE(snapshot_->CancelUpdate()); + } else { + LOG(INFO) << "Skip cancelling update in ResetUpdate because /metadata is " + << "not mounted"; + } + + return true; +} + +bool DynamicPartitionControlAndroid::ListDynamicPartitionsForSlot( + uint32_t current_slot, std::vector<std::string>* partitions) { + if (!GetDynamicPartitionsFeatureFlag().IsEnabled()) { + LOG(ERROR) << "Dynamic partition is not enabled"; + return false; + } + + std::string device_dir_str; + TEST_AND_RETURN_FALSE(GetDeviceDir(&device_dir_str)); + base::FilePath device_dir(device_dir_str); + auto super_device = + device_dir.Append(GetSuperPartitionName(current_slot)).value(); + auto builder = LoadMetadataBuilder(super_device, current_slot); + TEST_AND_RETURN_FALSE(builder != nullptr); + + std::vector<std::string> result; + auto suffix = SlotSuffixForSlotNumber(current_slot); + for (const auto& group : builder->ListGroups()) { + for (const auto& partition : builder->ListPartitionsInGroup(group)) { + std::string_view partition_name = partition->name(); + if (!android::base::ConsumeSuffix(&partition_name, suffix)) { + continue; + } + result.emplace_back(partition_name); + } + } + *partitions = std::move(result); + return true; +} + +bool DynamicPartitionControlAndroid::VerifyExtentsForUntouchedPartitions( + uint32_t source_slot, + uint32_t target_slot, + const std::vector<std::string>& partitions) { + std::string device_dir_str; + TEST_AND_RETURN_FALSE(GetDeviceDir(&device_dir_str)); + base::FilePath device_dir(device_dir_str); + + auto source_super_device = + device_dir.Append(GetSuperPartitionName(source_slot)).value(); + auto source_builder = LoadMetadataBuilder(source_super_device, source_slot); + TEST_AND_RETURN_FALSE(source_builder != nullptr); + + auto target_super_device = + device_dir.Append(GetSuperPartitionName(target_slot)).value(); + auto target_builder = LoadMetadataBuilder(target_super_device, target_slot); + TEST_AND_RETURN_FALSE(target_builder != nullptr); + + return MetadataBuilder::VerifyExtentsAgainstSourceMetadata( + *source_builder, source_slot, *target_builder, target_slot, partitions); +} + +bool DynamicPartitionControlAndroid::ExpectMetadataMounted() { + // No need to mount metadata for non-Virtual A/B devices. + if (!GetVirtualAbFeatureFlag().IsEnabled()) { + return false; + } + // Intentionally not checking |metadata_device_| in Android mode. + // /metadata should always be mounted in Android mode. If it isn't, let caller + // fails when calling into SnapshotManager. + if (!IsRecovery()) { + return true; + } + // In recovery mode, explicitly check |metadata_device_|. + return metadata_device_ != nullptr; +} + +bool DynamicPartitionControlAndroid::EnsureMetadataMounted() { + // No need to mount metadata for non-Virtual A/B devices. + if (!GetVirtualAbFeatureFlag().IsEnabled()) { + return true; + } + + if (metadata_device_ == nullptr) { + metadata_device_ = snapshot_->EnsureMetadataMounted(); + } + return metadata_device_ != nullptr; +} + } // namespace chromeos_update_engine diff --git a/dynamic_partition_control_android.h b/dynamic_partition_control_android.h index 0ccab4ef..49967f6c 100644 --- a/dynamic_partition_control_android.h +++ b/dynamic_partition_control_android.h @@ -17,45 +17,274 @@ #ifndef UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_ANDROID_H_ #define UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_ANDROID_H_ -#include "update_engine/dynamic_partition_control_interface.h" - #include <memory> #include <set> #include <string> +#include <vector> + +#include <base/files/file_util.h> +#include <libsnapshot/auto_device.h> +#include <libsnapshot/snapshot.h> + +#include "update_engine/common/dynamic_partition_control_interface.h" namespace chromeos_update_engine { class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface { public: - DynamicPartitionControlAndroid() = default; + DynamicPartitionControlAndroid(); ~DynamicPartitionControlAndroid(); - bool IsDynamicPartitionsEnabled() override; - bool IsDynamicPartitionsRetrofit() override; - bool MapPartitionOnDeviceMapper(const std::string& super_device, - const std::string& target_partition_name, - uint32_t slot, - bool force_writable, - std::string* path) override; - bool UnmapPartitionOnDeviceMapper(const std::string& target_partition_name, - bool wait) override; + FeatureFlag GetDynamicPartitionsFeatureFlag() override; + FeatureFlag GetVirtualAbFeatureFlag() override; + bool OptimizeOperation(const std::string& partition_name, + const InstallOperation& operation, + InstallOperation* optimized) override; void Cleanup() override; - bool DeviceExists(const std::string& path) override; - android::dm::DmDeviceState GetState(const std::string& name) override; - bool GetDmDevicePathByName(const std::string& name, - std::string* path) override; - std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder( - const std::string& super_device, + + bool PreparePartitionsForUpdate(uint32_t source_slot, + uint32_t target_slot, + const DeltaArchiveManifest& manifest, + bool update, + uint64_t* required_size) override; + bool FinishUpdate(bool powerwash_required) override; + std::unique_ptr<AbstractAction> GetCleanupPreviousUpdateAction( + BootControlInterface* boot_control, + PrefsInterface* prefs, + CleanupPreviousUpdateActionDelegateInterface* delegate) override; + + bool ResetUpdate(PrefsInterface* prefs) override; + + bool ListDynamicPartitionsForSlot( + uint32_t current_slot, std::vector<std::string>* partitions) override; + + bool VerifyExtentsForUntouchedPartitions( uint32_t source_slot, - uint32_t target_slot) override; - bool StoreMetadata(const std::string& super_device, - android::fs_mgr::MetadataBuilder* builder, - uint32_t target_slot) override; + uint32_t target_slot, + const std::vector<std::string>& partitions) override; + bool GetDeviceDir(std::string* path) override; + // Return the device for partition |partition_name| at slot |slot|. + // |current_slot| should be set to the current active slot. + // Note: this function is only used by BootControl*::GetPartitionDevice. + // Other callers should prefer BootControl*::GetPartitionDevice over + // BootControl*::GetDynamicPartitionControl()->GetPartitionDevice(). + bool GetPartitionDevice(const std::string& partition_name, + uint32_t slot, + uint32_t current_slot, + bool not_in_payload, + std::string* device, + bool* is_dynamic); + + bool GetPartitionDevice(const std::string& partition_name, + uint32_t slot, + uint32_t current_slot, + std::string* device); + + protected: + // These functions are exposed for testing. + + // Unmap logical partition on device mapper. This is the reverse operation + // of MapPartitionOnDeviceMapper. + // Returns true if unmapped successfully. + virtual bool UnmapPartitionOnDeviceMapper( + const std::string& target_partition_name); + + // Retrieves metadata from |super_device| at slot |slot|. + virtual std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder( + const std::string& super_device, uint32_t slot); + + // Retrieves metadata from |super_device| at slot |source_slot|. And modifies + // the metadata so that during updates, the metadata can be written to + // |target_slot|. In particular, on retrofit devices, the returned metadata + // automatically includes block devices at |target_slot|. + virtual std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder( + const std::string& super_device, + uint32_t source_slot, + uint32_t target_slot); + + // Write metadata |builder| to |super_device| at slot |target_slot|. + virtual bool StoreMetadata(const std::string& super_device, + android::fs_mgr::MetadataBuilder* builder, + uint32_t target_slot); + + // Map logical partition on device-mapper. + // |super_device| is the device path of the physical partition ("super"). + // |target_partition_name| is the identifier used in metadata; for example, + // "vendor_a" + // |slot| is the selected slot to mount; for example, 0 for "_a". + // Returns true if mapped successfully; if so, |path| is set to the device + // path of the mapped logical partition. + virtual bool MapPartitionOnDeviceMapper( + const std::string& super_device, + const std::string& target_partition_name, + uint32_t slot, + bool force_writable, + std::string* path); + + // Return true if a static partition exists at device path |path|. + virtual bool DeviceExists(const std::string& path); + + // Returns the current state of the underlying device mapper device + // with given name. + // One of INVALID, SUSPENDED or ACTIVE. + virtual android::dm::DmDeviceState GetState(const std::string& name); + + // Returns the path to the device mapper device node in '/dev' corresponding + // to 'name'. If the device does not exist, false is returned, and the path + // parameter is not set. + virtual bool GetDmDevicePathByName(const std::string& name, + std::string* path); + + // Return the name of the super partition (which stores super partition + // metadata) for a given slot. + virtual std::string GetSuperPartitionName(uint32_t slot); + + virtual void set_fake_mapped_devices(const std::set<std::string>& fake); + + // Allow mock objects to override this to test recovery mode. + virtual bool IsRecovery(); + + // Determine path for system_other partition. + // |source_slot| should be current slot. + // |target_slot| should be "other" slot. + // |partition_name_suffix| should be "system" + suffix(|target_slot|). + // Return true and set |path| if successful. + // Set |path| to empty if no need to erase system_other. + // Set |should_unmap| to true if path needs to be unmapped later. + // + // Note: system_other cannot use GetPartitionDevice or + // GetDynamicPartitionDevice because: + // - super partition metadata may be loaded from the source slot + // - UPDATED flag needs to be check to skip erasing if partition is not + // created by flashing tools + // - Snapshots from previous update attempts should not be used. + virtual bool GetSystemOtherPath(uint32_t source_slot, + uint32_t target_slot, + const std::string& partition_name_suffix, + std::string* path, + bool* should_unmap); + + // Returns true if any entry in the fstab file in |path| has AVB enabled, + // false if not enabled, and nullopt for any error. + virtual std::optional<bool> IsAvbEnabledInFstab(const std::string& path); + + // Returns true if system_other has AVB enabled, false if not enabled, and + // nullopt for any error. + virtual std::optional<bool> IsAvbEnabledOnSystemOther(); + + // Erase system_other partition that may contain system_other.img. + // After the update, the content of system_other may be corrupted but with + // valid AVB footer. If the update is rolled back and factory data reset is + // triggered, system_b fails to be mapped with verity errors (see + // b/152444348). Erase the system_other so that mapping system_other is + // skipped. + virtual bool EraseSystemOtherAvbFooter(uint32_t source_slot, + uint32_t target_slot); + + // Helper for PreparePartitionsForUpdate. Used for devices with dynamic + // partitions updating without snapshots. + // If |delete_source| is set, source partitions are deleted before resizing + // target partitions (using DeleteSourcePartitions). + virtual bool PrepareDynamicPartitionsForUpdate( + uint32_t source_slot, + uint32_t target_slot, + const DeltaArchiveManifest& manifest, + bool delete_source); + private: - std::set<std::string> mapped_devices_; + friend class DynamicPartitionControlAndroidTest; + friend class SnapshotPartitionTestP; - void CleanupInternal(bool wait); + void UnmapAllPartitions(); + bool MapPartitionInternal(const std::string& super_device, + const std::string& target_partition_name, + uint32_t slot, + bool force_writable, + std::string* path); + + // Update |builder| according to |partition_metadata|. + // - In Android mode, this is only called when the device + // does not have Virtual A/B. + // - When sideloading, this maybe called as a fallback path if CoW cannot + // be created. + bool UpdatePartitionMetadata(android::fs_mgr::MetadataBuilder* builder, + uint32_t target_slot, + const DeltaArchiveManifest& manifest); + + // Helper for PreparePartitionsForUpdate. Used for snapshotted partitions for + // Virtual A/B update. + bool PrepareSnapshotPartitionsForUpdate(uint32_t source_slot, + uint32_t target_slot, + const DeltaArchiveManifest& manifest, + uint64_t* required_size); + + enum class DynamicPartitionDeviceStatus { + SUCCESS, + ERROR, + TRY_STATIC, + }; + + // Return SUCCESS and path in |device| if partition is dynamic. + // Return ERROR if any error. + // Return TRY_STATIC if caller should resolve the partition as a static + // partition instead. + DynamicPartitionDeviceStatus GetDynamicPartitionDevice( + const base::FilePath& device_dir, + const std::string& partition_name_suffix, + uint32_t slot, + uint32_t current_slot, + bool not_in_payload, + std::string* device); + + // Return true if |partition_name_suffix| is a block device of + // super partition metadata slot |slot|. + bool IsSuperBlockDevice(const base::FilePath& device_dir, + uint32_t current_slot, + const std::string& partition_name_suffix); + + // If sideloading a full OTA, delete source partitions from |builder|. + bool DeleteSourcePartitions(android::fs_mgr::MetadataBuilder* builder, + uint32_t source_slot, + const DeltaArchiveManifest& manifest); + + // Returns true if metadata is expected to be mounted, false otherwise. + // Note that it returns false on non-Virtual A/B devices. + // + // Almost all functions of SnapshotManager depends on metadata being mounted. + // - In Android mode for Virtual A/B devices, assume it is mounted. If not, + // let caller fails when calling into SnapshotManager. + // - In recovery for Virtual A/B devices, it is possible that metadata is not + // formatted, hence it cannot be mounted. Caller should not call into + // SnapshotManager. + // - On non-Virtual A/B devices, updates do not depend on metadata partition. + // Caller should not call into SnapshotManager. + // + // This function does NOT mount metadata partition. Use EnsureMetadataMounted + // to mount metadata partition. + bool ExpectMetadataMounted(); + + // Ensure /metadata is mounted. Returns true if successful, false otherwise. + // + // Note that this function returns true on non-Virtual A/B devices without + // doing anything. + bool EnsureMetadataMounted(); + + // Set boolean flags related to target build. This includes flags like + // target_supports_snapshot_ and is_target_dynamic_. + bool SetTargetBuildVars(const DeltaArchiveManifest& manifest); + + std::set<std::string> mapped_devices_; + const FeatureFlag dynamic_partitions_; + const FeatureFlag virtual_ab_; + std::unique_ptr<android::snapshot::ISnapshotManager> snapshot_; + std::unique_ptr<android::snapshot::AutoDevice> metadata_device_; + bool target_supports_snapshot_ = false; + // Whether the target partitions should be loaded as dynamic partitions. Set + // by PreparePartitionsForUpdate() per each update. + bool is_target_dynamic_ = false; + uint32_t source_slot_ = UINT32_MAX; + uint32_t target_slot_ = UINT32_MAX; DISALLOW_COPY_AND_ASSIGN(DynamicPartitionControlAndroid); }; diff --git a/dynamic_partition_control_android_unittest.cc b/dynamic_partition_control_android_unittest.cc new file mode 100644 index 00000000..223e177d --- /dev/null +++ b/dynamic_partition_control_android_unittest.cc @@ -0,0 +1,1030 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/dynamic_partition_control_android.h" + +#include <set> +#include <vector> + +#include <base/logging.h> +#include <base/strings/string_util.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <libavb/libavb.h> +#include <libsnapshot/mock_snapshot.h> + +#include "update_engine/common/mock_prefs.h" +#include "update_engine/common/test_utils.h" +#include "update_engine/dynamic_partition_test_utils.h" +#include "update_engine/mock_dynamic_partition_control.h" + +using android::dm::DmDeviceState; +using android::snapshot::MockSnapshotManager; +using chromeos_update_engine::test_utils::ScopedLoopbackDeviceBinder; +using chromeos_update_engine::test_utils::ScopedTempFile; +using std::string; +using testing::_; +using testing::AnyNumber; +using testing::AnyOf; +using testing::Invoke; +using testing::NiceMock; +using testing::Not; +using testing::Optional; +using testing::Return; + +namespace chromeos_update_engine { + +class DynamicPartitionControlAndroidTest : public ::testing::Test { + public: + void SetUp() override { + module_ = std::make_unique<NiceMock<MockDynamicPartitionControlAndroid>>(); + + ON_CALL(dynamicControl(), GetDynamicPartitionsFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); + ON_CALL(dynamicControl(), GetVirtualAbFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::NONE))); + + ON_CALL(dynamicControl(), GetDeviceDir(_)) + .WillByDefault(Invoke([](auto path) { + *path = kFakeDevicePath; + return true; + })); + + ON_CALL(dynamicControl(), GetSuperPartitionName(_)) + .WillByDefault(Return(kFakeSuper)); + + ON_CALL(dynamicControl(), GetDmDevicePathByName(_, _)) + .WillByDefault(Invoke([](auto partition_name_suffix, auto device) { + *device = GetDmDevice(partition_name_suffix); + return true; + })); + + ON_CALL(dynamicControl(), EraseSystemOtherAvbFooter(_, _)) + .WillByDefault(Return(true)); + + ON_CALL(dynamicControl(), IsRecovery()).WillByDefault(Return(false)); + + ON_CALL(dynamicControl(), PrepareDynamicPartitionsForUpdate(_, _, _, _)) + .WillByDefault(Invoke([&](uint32_t source_slot, + uint32_t target_slot, + const DeltaArchiveManifest& manifest, + bool delete_source) { + return dynamicControl().RealPrepareDynamicPartitionsForUpdate( + source_slot, target_slot, manifest, delete_source); + })); + } + + // Return the mocked DynamicPartitionControlInterface. + NiceMock<MockDynamicPartitionControlAndroid>& dynamicControl() { + return static_cast<NiceMock<MockDynamicPartitionControlAndroid>&>(*module_); + } + + std::string GetSuperDevice(uint32_t slot) { + return GetDevice(dynamicControl().GetSuperPartitionName(slot)); + } + + uint32_t source() { return slots_.source; } + uint32_t target() { return slots_.target; } + + // Return partition names with suffix of source(). + std::string S(const std::string& name) { + return name + kSlotSuffixes[source()]; + } + + // Return partition names with suffix of target(). + std::string T(const std::string& name) { + return name + kSlotSuffixes[target()]; + } + + // Set the fake metadata to return when LoadMetadataBuilder is called on + // |slot|. + void SetMetadata(uint32_t slot, + const PartitionSuffixSizes& sizes, + uint32_t partition_attr = 0, + uint64_t super_size = kDefaultSuperSize) { + EXPECT_CALL(dynamicControl(), + LoadMetadataBuilder(GetSuperDevice(slot), slot)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke([=](auto, auto) { + return NewFakeMetadata(PartitionSuffixSizesToManifest(sizes), + partition_attr, + super_size); + })); + + EXPECT_CALL(dynamicControl(), + LoadMetadataBuilder(GetSuperDevice(slot), slot, _)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke([=](auto, auto, auto) { + return NewFakeMetadata(PartitionSuffixSizesToManifest(sizes), + partition_attr, + super_size); + })); + } + + void ExpectStoreMetadata(const PartitionSuffixSizes& partition_sizes) { + EXPECT_CALL(dynamicControl(), + StoreMetadata(GetSuperDevice(target()), + MetadataMatches(partition_sizes), + target())) + .WillOnce(Return(true)); + } + + // Expect that UnmapPartitionOnDeviceMapper is called on target() metadata + // slot with each partition in |partitions|. + void ExpectUnmap(const std::set<std::string>& partitions) { + // Error when UnmapPartitionOnDeviceMapper is called on unknown arguments. + ON_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(_)) + .WillByDefault(Return(false)); + + for (const auto& partition : partitions) { + EXPECT_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(partition)) + .WillOnce(Return(true)); + } + } + bool PreparePartitionsForUpdate(const PartitionSizes& partition_sizes) { + return dynamicControl().PreparePartitionsForUpdate( + source(), + target(), + PartitionSizesToManifest(partition_sizes), + true, + nullptr); + } + void SetSlots(const TestParam& slots) { slots_ = slots; } + + void SetSnapshotEnabled(bool enabled) { + dynamicControl().target_supports_snapshot_ = enabled; + } + + struct Listener : public ::testing::MatchResultListener { + explicit Listener(std::ostream* os) : MatchResultListener(os) {} + }; + + testing::AssertionResult UpdatePartitionMetadata( + const PartitionSuffixSizes& source_metadata, + const PartitionSizes& update_metadata, + const PartitionSuffixSizes& expected) { + return UpdatePartitionMetadata( + PartitionSuffixSizesToManifest(source_metadata), + PartitionSizesToManifest(update_metadata), + PartitionSuffixSizesToManifest(expected)); + } + testing::AssertionResult UpdatePartitionMetadata( + const DeltaArchiveManifest& source_manifest, + const DeltaArchiveManifest& update_manifest, + const DeltaArchiveManifest& expected) { + return UpdatePartitionMetadata( + source_manifest, update_manifest, MetadataMatches(expected)); + } + testing::AssertionResult UpdatePartitionMetadata( + const DeltaArchiveManifest& source_manifest, + const DeltaArchiveManifest& update_manifest, + const Matcher<MetadataBuilder*>& matcher) { + auto super_metadata = NewFakeMetadata(source_manifest); + if (!module_->UpdatePartitionMetadata( + super_metadata.get(), target(), update_manifest)) { + return testing::AssertionFailure() + << "UpdatePartitionMetadataInternal failed"; + } + std::stringstream ss; + Listener listener(&ss); + if (matcher.MatchAndExplain(super_metadata.get(), &listener)) { + return testing::AssertionSuccess() << ss.str(); + } else { + return testing::AssertionFailure() << ss.str(); + } + } + + std::unique_ptr<DynamicPartitionControlAndroid> module_; + TestParam slots_; +}; + +class DynamicPartitionControlAndroidTestP + : public DynamicPartitionControlAndroidTest, + public ::testing::WithParamInterface<TestParam> { + public: + void SetUp() override { + DynamicPartitionControlAndroidTest::SetUp(); + SetSlots(GetParam()); + } +}; + +// Test resize case. Grow if target metadata contains a partition with a size +// less than expected. +TEST_P(DynamicPartitionControlAndroidTestP, + NeedGrowIfSizeNotMatchWhenResizing) { + PartitionSuffixSizes source_metadata{{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}; + PartitionSuffixSizes expected{{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 3_GiB}, + {T("vendor"), 1_GiB}}; + PartitionSizes update_metadata{{"system", 3_GiB}, {"vendor", 1_GiB}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +// Test resize case. Shrink if target metadata contains a partition with a size +// greater than expected. +TEST_P(DynamicPartitionControlAndroidTestP, + NeedShrinkIfSizeNotMatchWhenResizing) { + PartitionSuffixSizes source_metadata{{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}; + PartitionSuffixSizes expected{{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 150_MiB}}; + PartitionSizes update_metadata{{"system", 2_GiB}, {"vendor", 150_MiB}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +// Test adding partitions on the first run. +TEST_P(DynamicPartitionControlAndroidTestP, AddPartitionToEmptyMetadata) { + PartitionSuffixSizes source_metadata{}; + PartitionSuffixSizes expected{{T("system"), 2_GiB}, {T("vendor"), 1_GiB}}; + PartitionSizes update_metadata{{"system", 2_GiB}, {"vendor", 1_GiB}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +// Test subsequent add case. +TEST_P(DynamicPartitionControlAndroidTestP, AddAdditionalPartition) { + PartitionSuffixSizes source_metadata{{S("system"), 2_GiB}, + {T("system"), 2_GiB}}; + PartitionSuffixSizes expected{ + {S("system"), 2_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}}; + PartitionSizes update_metadata{{"system", 2_GiB}, {"vendor", 1_GiB}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +// Test delete one partition. +TEST_P(DynamicPartitionControlAndroidTestP, DeletePartition) { + PartitionSuffixSizes source_metadata{{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}; + // No T("vendor") + PartitionSuffixSizes expected{ + {S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 2_GiB}}; + PartitionSizes update_metadata{{"system", 2_GiB}}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +// Test delete all partitions. +TEST_P(DynamicPartitionControlAndroidTestP, DeleteAll) { + PartitionSuffixSizes source_metadata{{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}; + PartitionSuffixSizes expected{{S("system"), 2_GiB}, {S("vendor"), 1_GiB}}; + PartitionSizes update_metadata{}; + EXPECT_TRUE( + UpdatePartitionMetadata(source_metadata, update_metadata, expected)); +} + +// Test corrupt source metadata case. +TEST_P(DynamicPartitionControlAndroidTestP, CorruptedSourceMetadata) { + EXPECT_CALL(dynamicControl(), + LoadMetadataBuilder(GetSuperDevice(source()), source(), _)) + .WillOnce(Invoke([](auto, auto, auto) { return nullptr; })); + ExpectUnmap({T("system")}); + + EXPECT_FALSE(PreparePartitionsForUpdate({{"system", 1_GiB}})) + << "Should not be able to continue with corrupt source metadata"; +} + +// Test that UpdatePartitionMetadata fails if there is not enough space on the +// device. +TEST_P(DynamicPartitionControlAndroidTestP, NotEnoughSpace) { + PartitionSuffixSizes source_metadata{{S("system"), 3_GiB}, + {S("vendor"), 2_GiB}, + {T("system"), 0}, + {T("vendor"), 0}}; + PartitionSizes update_metadata{{"system", 3_GiB}, {"vendor", 3_GiB}}; + + EXPECT_FALSE(UpdatePartitionMetadata(source_metadata, update_metadata, {})) + << "Should not be able to fit 11GiB data into 10GiB space"; +} + +TEST_P(DynamicPartitionControlAndroidTestP, NotEnoughSpaceForSlot) { + PartitionSuffixSizes source_metadata{{S("system"), 1_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 0}, + {T("vendor"), 0}}; + PartitionSizes update_metadata{{"system", 3_GiB}, {"vendor", 3_GiB}}; + EXPECT_FALSE(UpdatePartitionMetadata(source_metadata, update_metadata, {})) + << "Should not be able to grow over size of super / 2"; +} + +TEST_P(DynamicPartitionControlAndroidTestP, + ApplyRetrofitUpdateOnDynamicPartitionsEnabledBuild) { + ON_CALL(dynamicControl(), GetDynamicPartitionsFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::RETROFIT))); + // Static partition {system,bar}_{a,b} exists. + EXPECT_CALL(dynamicControl(), + DeviceExists(AnyOf(GetDevice(S("bar")), + GetDevice(T("bar")), + GetDevice(S("system")), + GetDevice(T("system"))))) + .WillRepeatedly(Return(true)); + + SetMetadata(source(), + {{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}); + + // Not calling through + // DynamicPartitionControlAndroidTest::PreparePartitionsForUpdate(), since we + // don't want any default group in the PartitionMetadata. + EXPECT_TRUE(dynamicControl().PreparePartitionsForUpdate( + source(), target(), {}, true, nullptr)); + + // Should use dynamic source partitions. + EXPECT_CALL(dynamicControl(), GetState(S("system"))) + .Times(1) + .WillOnce(Return(DmDeviceState::ACTIVE)); + string system_device; + EXPECT_TRUE(dynamicControl().GetPartitionDevice( + "system", source(), source(), &system_device)); + EXPECT_EQ(GetDmDevice(S("system")), system_device); + + // Should use static target partitions without querying dynamic control. + EXPECT_CALL(dynamicControl(), GetState(T("system"))).Times(0); + EXPECT_TRUE(dynamicControl().GetPartitionDevice( + "system", target(), source(), &system_device)); + EXPECT_EQ(GetDevice(T("system")), system_device); + + // Static partition "bar". + EXPECT_CALL(dynamicControl(), GetState(S("bar"))).Times(0); + std::string bar_device; + EXPECT_TRUE(dynamicControl().GetPartitionDevice( + "bar", source(), source(), &bar_device)); + EXPECT_EQ(GetDevice(S("bar")), bar_device); + + EXPECT_CALL(dynamicControl(), GetState(T("bar"))).Times(0); + EXPECT_TRUE(dynamicControl().GetPartitionDevice( + "bar", target(), source(), &bar_device)); + EXPECT_EQ(GetDevice(T("bar")), bar_device); +} + +TEST_P(DynamicPartitionControlAndroidTestP, + GetPartitionDeviceWhenResumingUpdate) { + // Static partition bar_{a,b} exists. + EXPECT_CALL(dynamicControl(), + DeviceExists(AnyOf(GetDevice(S("bar")), GetDevice(T("bar"))))) + .WillRepeatedly(Return(true)); + + // Both of the two slots contain valid partition metadata, since this is + // resuming an update. + SetMetadata(source(), + {{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}); + SetMetadata(target(), + {{S("system"), 2_GiB}, + {S("vendor"), 1_GiB}, + {T("system"), 2_GiB}, + {T("vendor"), 1_GiB}}); + + EXPECT_TRUE(dynamicControl().PreparePartitionsForUpdate( + source(), + target(), + PartitionSizesToManifest({{"system", 2_GiB}, {"vendor", 1_GiB}}), + false, + nullptr)); + + // Dynamic partition "system". + EXPECT_CALL(dynamicControl(), GetState(S("system"))) + .Times(1) + .WillOnce(Return(DmDeviceState::ACTIVE)); + string system_device; + EXPECT_TRUE(dynamicControl().GetPartitionDevice( + "system", source(), source(), &system_device)); + EXPECT_EQ(GetDmDevice(S("system")), system_device); + + EXPECT_CALL(dynamicControl(), GetState(T("system"))) + .Times(AnyNumber()) + .WillOnce(Return(DmDeviceState::ACTIVE)); + EXPECT_CALL(dynamicControl(), + MapPartitionOnDeviceMapper( + GetSuperDevice(target()), T("system"), target(), _, _)) + .Times(AnyNumber()) + .WillRepeatedly( + Invoke([](const auto&, const auto& name, auto, auto, auto* device) { + *device = "/fake/remapped/" + name; + return true; + })); + EXPECT_TRUE(dynamicControl().GetPartitionDevice( + "system", target(), source(), &system_device)); + EXPECT_EQ("/fake/remapped/" + T("system"), system_device); + + // Static partition "bar". + EXPECT_CALL(dynamicControl(), GetState(S("bar"))).Times(0); + std::string bar_device; + EXPECT_TRUE(dynamicControl().GetPartitionDevice( + "bar", source(), source(), &bar_device)); + EXPECT_EQ(GetDevice(S("bar")), bar_device); + + EXPECT_CALL(dynamicControl(), GetState(T("bar"))).Times(0); + EXPECT_TRUE(dynamicControl().GetPartitionDevice( + "bar", target(), source(), &bar_device)); + EXPECT_EQ(GetDevice(T("bar")), bar_device); +} + +INSTANTIATE_TEST_CASE_P(DynamicPartitionControlAndroidTest, + DynamicPartitionControlAndroidTestP, + testing::Values(TestParam{0, 1}, TestParam{1, 0})); + +class DynamicPartitionControlAndroidGroupTestP + : public DynamicPartitionControlAndroidTestP { + public: + DeltaArchiveManifest source_manifest; + void SetUp() override { + DynamicPartitionControlAndroidTestP::SetUp(); + AddGroupAndPartition( + &source_manifest, S("android"), 3_GiB, S("system"), 2_GiB); + AddGroupAndPartition(&source_manifest, S("oem"), 2_GiB, S("vendor"), 1_GiB); + AddGroupAndPartition(&source_manifest, T("android"), 3_GiB, T("system"), 0); + AddGroupAndPartition(&source_manifest, T("oem"), 2_GiB, T("vendor"), 0); + } + + void AddGroupAndPartition(DeltaArchiveManifest* manifest, + const string& group, + uint64_t group_size, + const string& partition, + uint64_t partition_size) { + auto* g = AddGroup(manifest, group, group_size); + AddPartition(manifest, g, partition, partition_size); + } +}; + +// Allow to resize within group. +TEST_P(DynamicPartitionControlAndroidGroupTestP, ResizeWithinGroup) { + DeltaArchiveManifest expected; + AddGroupAndPartition(&expected, T("android"), 3_GiB, T("system"), 3_GiB); + AddGroupAndPartition(&expected, T("oem"), 2_GiB, T("vendor"), 2_GiB); + + DeltaArchiveManifest update_manifest; + AddGroupAndPartition(&update_manifest, "android", 3_GiB, "system", 3_GiB); + AddGroupAndPartition(&update_manifest, "oem", 2_GiB, "vendor", 2_GiB); + + EXPECT_TRUE( + UpdatePartitionMetadata(source_manifest, update_manifest, expected)); +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, NotEnoughSpaceForGroup) { + DeltaArchiveManifest update_manifest; + AddGroupAndPartition(&update_manifest, "android", 3_GiB, "system", 1_GiB), + AddGroupAndPartition(&update_manifest, "oem", 2_GiB, "vendor", 3_GiB); + EXPECT_FALSE(UpdatePartitionMetadata(source_manifest, update_manifest, {})) + << "Should not be able to grow over maximum size of group"; +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, GroupTooBig) { + DeltaArchiveManifest update_manifest; + AddGroup(&update_manifest, "android", 3_GiB); + AddGroup(&update_manifest, "oem", 3_GiB); + EXPECT_FALSE(UpdatePartitionMetadata(source_manifest, update_manifest, {})) + << "Should not be able to grow over size of super / 2"; +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, AddPartitionToGroup) { + DeltaArchiveManifest expected; + auto* g = AddGroup(&expected, T("android"), 3_GiB); + AddPartition(&expected, g, T("system"), 2_GiB); + AddPartition(&expected, g, T("system_ext"), 1_GiB); + + DeltaArchiveManifest update_manifest; + g = AddGroup(&update_manifest, "android", 3_GiB); + AddPartition(&update_manifest, g, "system", 2_GiB); + AddPartition(&update_manifest, g, "system_ext", 1_GiB); + AddGroupAndPartition(&update_manifest, "oem", 2_GiB, "vendor", 2_GiB); + + EXPECT_TRUE( + UpdatePartitionMetadata(source_manifest, update_manifest, expected)); +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, RemovePartitionFromGroup) { + DeltaArchiveManifest expected; + AddGroup(&expected, T("android"), 3_GiB); + + DeltaArchiveManifest update_manifest; + AddGroup(&update_manifest, "android", 3_GiB); + AddGroupAndPartition(&update_manifest, "oem", 2_GiB, "vendor", 2_GiB); + + EXPECT_TRUE( + UpdatePartitionMetadata(source_manifest, update_manifest, expected)); +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, AddGroup) { + DeltaArchiveManifest expected; + AddGroupAndPartition( + &expected, T("new_group"), 2_GiB, T("new_partition"), 2_GiB); + + DeltaArchiveManifest update_manifest; + AddGroupAndPartition(&update_manifest, "android", 2_GiB, "system", 2_GiB); + AddGroupAndPartition(&update_manifest, "oem", 1_GiB, "vendor", 1_GiB); + AddGroupAndPartition( + &update_manifest, "new_group", 2_GiB, "new_partition", 2_GiB); + EXPECT_TRUE( + UpdatePartitionMetadata(source_manifest, update_manifest, expected)); +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, RemoveGroup) { + DeltaArchiveManifest update_manifest; + AddGroupAndPartition(&update_manifest, "android", 2_GiB, "system", 2_GiB); + + EXPECT_TRUE(UpdatePartitionMetadata( + source_manifest, update_manifest, Not(HasGroup(T("oem"))))); +} + +TEST_P(DynamicPartitionControlAndroidGroupTestP, ResizeGroup) { + DeltaArchiveManifest expected; + AddGroupAndPartition(&expected, T("android"), 2_GiB, T("system"), 2_GiB); + AddGroupAndPartition(&expected, T("oem"), 3_GiB, T("vendor"), 3_GiB); + DeltaArchiveManifest update_manifest; + AddGroupAndPartition(&update_manifest, "android", 2_GiB, "system", 2_GiB), + AddGroupAndPartition(&update_manifest, "oem", 3_GiB, "vendor", 3_GiB); + EXPECT_TRUE( + UpdatePartitionMetadata(source_manifest, update_manifest, expected)); +} + +INSTANTIATE_TEST_CASE_P(DynamicPartitionControlAndroidTest, + DynamicPartitionControlAndroidGroupTestP, + testing::Values(TestParam{0, 1}, TestParam{1, 0})); + +const PartitionSuffixSizes update_sizes_0() { + // Initial state is 0 for "other" slot. + return { + {"grown_a", 2_GiB}, + {"shrunk_a", 1_GiB}, + {"same_a", 100_MiB}, + {"deleted_a", 150_MiB}, + // no added_a + {"grown_b", 200_MiB}, + // simulate system_other + {"shrunk_b", 0}, + {"same_b", 0}, + {"deleted_b", 0}, + // no added_b + }; +} + +const PartitionSuffixSizes update_sizes_1() { + return { + {"grown_a", 2_GiB}, + {"shrunk_a", 1_GiB}, + {"same_a", 100_MiB}, + {"deleted_a", 150_MiB}, + // no added_a + {"grown_b", 3_GiB}, + {"shrunk_b", 150_MiB}, + {"same_b", 100_MiB}, + {"added_b", 150_MiB}, + // no deleted_b + }; +} + +const PartitionSuffixSizes update_sizes_2() { + return { + {"grown_a", 4_GiB}, + {"shrunk_a", 100_MiB}, + {"same_a", 100_MiB}, + {"deleted_a", 64_MiB}, + // no added_a + {"grown_b", 3_GiB}, + {"shrunk_b", 150_MiB}, + {"same_b", 100_MiB}, + {"added_b", 150_MiB}, + // no deleted_b + }; +} + +// Test case for first update after the device is manufactured, in which +// case the "other" slot is likely of size "0" (except system, which is +// non-zero because of system_other partition) +TEST_F(DynamicPartitionControlAndroidTest, SimulatedFirstUpdate) { + SetSlots({0, 1}); + + SetMetadata(source(), update_sizes_0()); + SetMetadata(target(), update_sizes_0()); + ExpectStoreMetadata(update_sizes_1()); + ExpectUnmap({"grown_b", "shrunk_b", "same_b", "added_b"}); + + EXPECT_TRUE(PreparePartitionsForUpdate({{"grown", 3_GiB}, + {"shrunk", 150_MiB}, + {"same", 100_MiB}, + {"added", 150_MiB}})); +} + +// After first update, test for the second update. In the second update, the +// "added" partition is deleted and "deleted" partition is re-added. +TEST_F(DynamicPartitionControlAndroidTest, SimulatedSecondUpdate) { + SetSlots({1, 0}); + + SetMetadata(source(), update_sizes_1()); + SetMetadata(target(), update_sizes_0()); + + ExpectStoreMetadata(update_sizes_2()); + ExpectUnmap({"grown_a", "shrunk_a", "same_a", "deleted_a"}); + + EXPECT_TRUE(PreparePartitionsForUpdate({{"grown", 4_GiB}, + {"shrunk", 100_MiB}, + {"same", 100_MiB}, + {"deleted", 64_MiB}})); +} + +TEST_F(DynamicPartitionControlAndroidTest, ApplyingToCurrentSlot) { + SetSlots({1, 1}); + EXPECT_FALSE(PreparePartitionsForUpdate({})) + << "Should not be able to apply to current slot."; +} + +TEST_P(DynamicPartitionControlAndroidTestP, OptimizeOperationTest) { + ASSERT_TRUE(dynamicControl().PreparePartitionsForUpdate( + source(), + target(), + PartitionSizesToManifest({{"foo", 4_MiB}}), + false, + nullptr)); + dynamicControl().set_fake_mapped_devices({T("foo")}); + + InstallOperation iop; + InstallOperation optimized; + Extent *se, *de; + + // Not a SOURCE_COPY operation, cannot skip. + iop.set_type(InstallOperation::REPLACE); + EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized)); + + iop.set_type(InstallOperation::SOURCE_COPY); + + // By default GetVirtualAbFeatureFlag is disabled. Cannot skip operation. + EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized)); + + // Enable GetVirtualAbFeatureFlag in the mock interface. + ON_CALL(dynamicControl(), GetVirtualAbFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); + + // By default target_supports_snapshot_ is set to false. Cannot skip + // operation. + EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized)); + + SetSnapshotEnabled(true); + + // Empty source and destination. Skip. + EXPECT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized)); + EXPECT_TRUE(optimized.src_extents().empty()); + EXPECT_TRUE(optimized.dst_extents().empty()); + + se = iop.add_src_extents(); + se->set_start_block(0); + se->set_num_blocks(1); + + // There is something in sources, but destinations are empty. Cannot skip. + EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized)); + + InstallOperation iop2; + + de = iop2.add_dst_extents(); + de->set_start_block(0); + de->set_num_blocks(1); + + // There is something in destinations, but sources are empty. Cannot skip. + EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop2, &optimized)); + + de = iop.add_dst_extents(); + de->set_start_block(0); + de->set_num_blocks(1); + + // Sources and destinations are identical. Skip. + EXPECT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized)); + EXPECT_TRUE(optimized.src_extents().empty()); + EXPECT_TRUE(optimized.dst_extents().empty()); + + se = iop.add_src_extents(); + se->set_start_block(1); + se->set_num_blocks(5); + + // There is something in source, but not in destination. Cannot skip. + EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized)); + + de = iop.add_dst_extents(); + de->set_start_block(1); + de->set_num_blocks(5); + + // There is source and destination are equal. Skip. + EXPECT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized)); + EXPECT_TRUE(optimized.src_extents().empty()); + EXPECT_TRUE(optimized.dst_extents().empty()); + + de = iop.add_dst_extents(); + de->set_start_block(6); + de->set_num_blocks(5); + + // There is something extra in dest. Cannot skip. + EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized)); + + se = iop.add_src_extents(); + se->set_start_block(6); + se->set_num_blocks(5); + + // Source and dest are identical again. Skip. + EXPECT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized)); + EXPECT_TRUE(optimized.src_extents().empty()); + EXPECT_TRUE(optimized.dst_extents().empty()); + + iop.Clear(); + iop.set_type(InstallOperation::SOURCE_COPY); + se = iop.add_src_extents(); + se->set_start_block(1); + se->set_num_blocks(1); + se = iop.add_src_extents(); + se->set_start_block(3); + se->set_num_blocks(2); + se = iop.add_src_extents(); + se->set_start_block(7); + se->set_num_blocks(2); + de = iop.add_dst_extents(); + de->set_start_block(2); + de->set_num_blocks(5); + + // [1, 3, 4, 7, 8] -> [2, 3, 4, 5, 6] should return [1, 7, 8] -> [2, 5, 6] + EXPECT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized)); + ASSERT_EQ(2, optimized.src_extents_size()); + ASSERT_EQ(2, optimized.dst_extents_size()); + EXPECT_EQ(1u, optimized.src_extents(0).start_block()); + EXPECT_EQ(1u, optimized.src_extents(0).num_blocks()); + EXPECT_EQ(2u, optimized.dst_extents(0).start_block()); + EXPECT_EQ(1u, optimized.dst_extents(0).num_blocks()); + EXPECT_EQ(7u, optimized.src_extents(1).start_block()); + EXPECT_EQ(2u, optimized.src_extents(1).num_blocks()); + EXPECT_EQ(5u, optimized.dst_extents(1).start_block()); + EXPECT_EQ(2u, optimized.dst_extents(1).num_blocks()); + + // Don't skip for static partitions. + EXPECT_FALSE(dynamicControl().OptimizeOperation("bar", iop, &optimized)); +} + +TEST_F(DynamicPartitionControlAndroidTest, ResetUpdate) { + MockPrefs prefs; + ASSERT_TRUE(dynamicControl().ResetUpdate(&prefs)); +} + +TEST_F(DynamicPartitionControlAndroidTest, IsAvbNotEnabledInFstab) { + // clang-format off + std::string fstab_content = + "system /postinstall ext4 ro,nosuid,nodev,noexec slotselect_other,logical\n" // NOLINT(whitespace/line_length) + "/dev/block/by-name/system /postinstall ext4 ro,nosuid,nodev,noexec slotselect_other\n"; // NOLINT(whitespace/line_length) + // clang-format on + ScopedTempFile fstab; + ASSERT_TRUE(test_utils::WriteFileString(fstab.path(), fstab_content)); + ASSERT_THAT(dynamicControl().RealIsAvbEnabledInFstab(fstab.path()), + Optional(false)); +} + +TEST_F(DynamicPartitionControlAndroidTest, IsAvbEnabledInFstab) { + // clang-format off + std::string fstab_content = + "system /postinstall ext4 ro,nosuid,nodev,noexec slotselect_other,logical,avb_keys=/foo\n"; // NOLINT(whitespace/line_length) + // clang-format on + ScopedTempFile fstab; + ASSERT_TRUE(test_utils::WriteFileString(fstab.path(), fstab_content)); + ASSERT_THAT(dynamicControl().RealIsAvbEnabledInFstab(fstab.path()), + Optional(true)); +} + +TEST_P(DynamicPartitionControlAndroidTestP, AvbNotEnabledOnSystemOther) { + ON_CALL(dynamicControl(), GetSystemOtherPath(_, _, _, _, _)) + .WillByDefault(Invoke([&](auto source_slot, + auto target_slot, + const auto& name, + auto path, + auto should_unmap) { + return dynamicControl().RealGetSystemOtherPath( + source_slot, target_slot, name, path, should_unmap); + })); + ON_CALL(dynamicControl(), IsAvbEnabledOnSystemOther()) + .WillByDefault(Return(false)); + EXPECT_TRUE( + dynamicControl().RealEraseSystemOtherAvbFooter(source(), target())); +} + +TEST_P(DynamicPartitionControlAndroidTestP, NoSystemOtherToErase) { + SetMetadata(source(), {{S("system"), 100_MiB}}); + ON_CALL(dynamicControl(), IsAvbEnabledOnSystemOther()) + .WillByDefault(Return(true)); + std::string path; + bool should_unmap; + ASSERT_TRUE(dynamicControl().RealGetSystemOtherPath( + source(), target(), T("system"), &path, &should_unmap)); + ASSERT_TRUE(path.empty()) << path; + ASSERT_FALSE(should_unmap); + ON_CALL(dynamicControl(), GetSystemOtherPath(_, _, _, _, _)) + .WillByDefault(Invoke([&](auto source_slot, + auto target_slot, + const auto& name, + auto path, + auto should_unmap) { + return dynamicControl().RealGetSystemOtherPath( + source_slot, target_slot, name, path, should_unmap); + })); + EXPECT_TRUE( + dynamicControl().RealEraseSystemOtherAvbFooter(source(), target())); +} + +TEST_P(DynamicPartitionControlAndroidTestP, SkipEraseUpdatedSystemOther) { + PartitionSuffixSizes sizes{{S("system"), 100_MiB}, {T("system"), 100_MiB}}; + SetMetadata(source(), sizes, LP_PARTITION_ATTR_UPDATED); + ON_CALL(dynamicControl(), IsAvbEnabledOnSystemOther()) + .WillByDefault(Return(true)); + std::string path; + bool should_unmap; + ASSERT_TRUE(dynamicControl().RealGetSystemOtherPath( + source(), target(), T("system"), &path, &should_unmap)); + ASSERT_TRUE(path.empty()) << path; + ASSERT_FALSE(should_unmap); + ON_CALL(dynamicControl(), GetSystemOtherPath(_, _, _, _, _)) + .WillByDefault(Invoke([&](auto source_slot, + auto target_slot, + const auto& name, + auto path, + auto should_unmap) { + return dynamicControl().RealGetSystemOtherPath( + source_slot, target_slot, name, path, should_unmap); + })); + EXPECT_TRUE( + dynamicControl().RealEraseSystemOtherAvbFooter(source(), target())); +} + +TEST_P(DynamicPartitionControlAndroidTestP, EraseSystemOtherAvbFooter) { + constexpr uint64_t file_size = 1_MiB; + static_assert(file_size > AVB_FOOTER_SIZE); + ScopedTempFile system_other; + brillo::Blob original(file_size, 'X'); + ASSERT_TRUE(test_utils::WriteFileVector(system_other.path(), original)); + std::string mnt_path; + ScopedLoopbackDeviceBinder dev(system_other.path(), true, &mnt_path); + ASSERT_TRUE(dev.is_bound()); + + brillo::Blob device_content; + ASSERT_TRUE(utils::ReadFile(mnt_path, &device_content)); + ASSERT_EQ(original, device_content); + + PartitionSuffixSizes sizes{{S("system"), 100_MiB}, {T("system"), file_size}}; + SetMetadata(source(), sizes); + ON_CALL(dynamicControl(), IsAvbEnabledOnSystemOther()) + .WillByDefault(Return(true)); + EXPECT_CALL(dynamicControl(), + GetSystemOtherPath(source(), target(), T("system"), _, _)) + .WillRepeatedly( + Invoke([&](auto, auto, const auto&, auto path, auto should_unmap) { + *path = mnt_path; + *should_unmap = false; + return true; + })); + ASSERT_TRUE( + dynamicControl().RealEraseSystemOtherAvbFooter(source(), target())); + + device_content.clear(); + ASSERT_TRUE(utils::ReadFile(mnt_path, &device_content)); + brillo::Blob new_expected(original); + // Clear the last AVB_FOOTER_SIZE bytes. + new_expected.resize(file_size - AVB_FOOTER_SIZE); + new_expected.resize(file_size, '\0'); + ASSERT_EQ(new_expected, device_content); +} + +class FakeAutoDevice : public android::snapshot::AutoDevice { + public: + FakeAutoDevice() : AutoDevice("") {} +}; + +class SnapshotPartitionTestP : public DynamicPartitionControlAndroidTestP { + public: + void SetUp() override { + DynamicPartitionControlAndroidTestP::SetUp(); + ON_CALL(dynamicControl(), GetVirtualAbFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); + + snapshot_ = new NiceMock<MockSnapshotManager>(); + dynamicControl().snapshot_.reset(snapshot_); // takes ownership + EXPECT_CALL(*snapshot_, BeginUpdate()).WillOnce(Return(true)); + EXPECT_CALL(*snapshot_, EnsureMetadataMounted()) + .WillRepeatedly( + Invoke([]() { return std::make_unique<FakeAutoDevice>(); })); + + manifest_ = + PartitionSizesToManifest({{"system", 3_GiB}, {"vendor", 1_GiB}}); + } + void ExpectCreateUpdateSnapshots(android::snapshot::Return val) { + manifest_.mutable_dynamic_partition_metadata()->set_snapshot_enabled(true); + EXPECT_CALL(*snapshot_, CreateUpdateSnapshots(_)) + .WillRepeatedly(Invoke([&, val](const auto& manifest) { + // Deep comparison requires full protobuf library. Comparing the + // pointers are sufficient. + EXPECT_EQ(&manifest_, &manifest); + LOG(WARNING) << "CreateUpdateSnapshots returning " << val.string(); + return val; + })); + } + bool PreparePartitionsForUpdate(uint64_t* required_size) { + return dynamicControl().PreparePartitionsForUpdate( + source(), target(), manifest_, true /* update */, required_size); + } + MockSnapshotManager* snapshot_ = nullptr; + DeltaArchiveManifest manifest_; +}; + +// Test happy path of PreparePartitionsForUpdate on a Virtual A/B device. +TEST_P(SnapshotPartitionTestP, PreparePartitions) { + ExpectCreateUpdateSnapshots(android::snapshot::Return::Ok()); + uint64_t required_size = 0; + EXPECT_TRUE(PreparePartitionsForUpdate(&required_size)); + EXPECT_EQ(0u, required_size); +} + +// Test that if not enough space, required size returned by SnapshotManager is +// passed up. +TEST_P(SnapshotPartitionTestP, PreparePartitionsNoSpace) { + ExpectCreateUpdateSnapshots(android::snapshot::Return::NoSpace(1_GiB)); + uint64_t required_size = 0; + EXPECT_FALSE(PreparePartitionsForUpdate(&required_size)); + EXPECT_EQ(1_GiB, required_size); +} + +// Test that in recovery, use empty space in super partition for a snapshot +// update first. +TEST_P(SnapshotPartitionTestP, RecoveryUseSuperEmpty) { + ExpectCreateUpdateSnapshots(android::snapshot::Return::Ok()); + EXPECT_CALL(dynamicControl(), IsRecovery()).WillRepeatedly(Return(true)); + // Must not call PrepareDynamicPartitionsForUpdate if + // PrepareSnapshotPartitionsForUpdate succeeds. + EXPECT_CALL(dynamicControl(), PrepareDynamicPartitionsForUpdate(_, _, _, _)) + .Times(0); + uint64_t required_size = 0; + EXPECT_TRUE(PreparePartitionsForUpdate(&required_size)); + EXPECT_EQ(0u, required_size); +} + +// Test that in recovery, if CreateUpdateSnapshots throws an error, try +// the flashing path for full updates. +TEST_P(SnapshotPartitionTestP, RecoveryErrorShouldDeleteSource) { + // Expectation on PreparePartitionsForUpdate + ExpectCreateUpdateSnapshots(android::snapshot::Return::NoSpace(1_GiB)); + EXPECT_CALL(dynamicControl(), IsRecovery()).WillRepeatedly(Return(true)); + EXPECT_CALL(*snapshot_, CancelUpdate()).WillOnce(Return(true)); + EXPECT_CALL(dynamicControl(), PrepareDynamicPartitionsForUpdate(_, _, _, _)) + .WillRepeatedly(Invoke([&](auto source_slot, + auto target_slot, + const auto& manifest, + auto delete_source) { + EXPECT_EQ(source(), source_slot); + EXPECT_EQ(target(), target_slot); + // Deep comparison requires full protobuf library. Comparing the + // pointers are sufficient. + EXPECT_EQ(&manifest_, &manifest); + EXPECT_TRUE(delete_source); + return dynamicControl().RealPrepareDynamicPartitionsForUpdate( + source_slot, target_slot, manifest, delete_source); + })); + // Only one slot of space in super + uint64_t super_size = kDefaultGroupSize + 1_MiB; + // Expectation on PrepareDynamicPartitionsForUpdate + SetMetadata( + source(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}}, 0, super_size); + ExpectUnmap({T("system"), T("vendor")}); + // Expect that the source partitions aren't present in target super metadata. + ExpectStoreMetadata({{T("system"), 3_GiB}, {T("vendor"), 1_GiB}}); + + uint64_t required_size = 0; + EXPECT_TRUE(PreparePartitionsForUpdate(&required_size)); + EXPECT_EQ(0u, required_size); +} + +INSTANTIATE_TEST_CASE_P(DynamicPartitionControlAndroidTest, + SnapshotPartitionTestP, + testing::Values(TestParam{0, 1}, TestParam{1, 0})); + +} // namespace chromeos_update_engine diff --git a/dynamic_partition_control_interface.h b/dynamic_partition_control_interface.h deleted file mode 100644 index 86a07300..00000000 --- a/dynamic_partition_control_interface.h +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#ifndef UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_ -#define UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_ - -#include <stdint.h> - -#include <memory> -#include <string> - -#include <base/files/file_util.h> -#include <libdm/dm.h> -#include <liblp/builder.h> - -namespace chromeos_update_engine { - -class DynamicPartitionControlInterface { - public: - virtual ~DynamicPartitionControlInterface() = default; - - // Return true iff dynamic partitions is enabled on this device. - virtual bool IsDynamicPartitionsEnabled() = 0; - - // Return true iff dynamic partitions is retrofitted on this device. - virtual bool IsDynamicPartitionsRetrofit() = 0; - - // Map logical partition on device-mapper. - // |super_device| is the device path of the physical partition ("super"). - // |target_partition_name| is the identifier used in metadata; for example, - // "vendor_a" - // |slot| is the selected slot to mount; for example, 0 for "_a". - // Returns true if mapped successfully; if so, |path| is set to the device - // path of the mapped logical partition. - virtual bool MapPartitionOnDeviceMapper( - const std::string& super_device, - const std::string& target_partition_name, - uint32_t slot, - bool force_writable, - std::string* path) = 0; - - // Unmap logical partition on device mapper. This is the reverse operation - // of MapPartitionOnDeviceMapper. - // If |wait| is set, wait until the device is unmapped. - // Returns true if unmapped successfully. - virtual bool UnmapPartitionOnDeviceMapper( - const std::string& target_partition_name, bool wait) = 0; - - // Do necessary cleanups before destroying the object. - virtual void Cleanup() = 0; - - // Return true if a static partition exists at device path |path|. - virtual bool DeviceExists(const std::string& path) = 0; - - // Returns the current state of the underlying device mapper device - // with given name. - // One of INVALID, SUSPENDED or ACTIVE. - virtual android::dm::DmDeviceState GetState(const std::string& name) = 0; - - // Returns the path to the device mapper device node in '/dev' corresponding - // to 'name'. If the device does not exist, false is returned, and the path - // parameter is not set. - virtual bool GetDmDevicePathByName(const std::string& name, - std::string* path) = 0; - - // Retrieve metadata from |super_device| at slot |source_slot|. - // On retrofit devices, if |target_slot| != kInvalidSlot, the returned - // metadata automatically includes block devices at |target_slot|. - virtual std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder( - const std::string& super_device, - uint32_t source_slot, - uint32_t target_slot) = 0; - - // Write metadata |builder| to |super_device| at slot |target_slot|. - virtual bool StoreMetadata(const std::string& super_device, - android::fs_mgr::MetadataBuilder* builder, - uint32_t target_slot) = 0; - - // Return a possible location for devices listed by name. - virtual bool GetDeviceDir(std::string* path) = 0; -}; - -} // namespace chromeos_update_engine - -#endif // UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_ diff --git a/dynamic_partition_test_utils.h b/dynamic_partition_test_utils.h new file mode 100644 index 00000000..d701dce8 --- /dev/null +++ b/dynamic_partition_test_utils.h @@ -0,0 +1,288 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_DYNAMIC_PARTITION_TEST_UTILS_H_ +#define UPDATE_ENGINE_DYNAMIC_PARTITION_TEST_UTILS_H_ + +#include <stdint.h> + +#include <iostream> +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include <base/strings/string_util.h> +#include <fs_mgr.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <liblp/builder.h> +#include <storage_literals/storage_literals.h> + +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { + +using android::fs_mgr::MetadataBuilder; +using testing::_; +using testing::MakeMatcher; +using testing::Matcher; +using testing::MatcherInterface; +using testing::MatchResultListener; +using namespace android::storage_literals; // NOLINT(build/namespaces) + +constexpr const uint32_t kMaxNumSlots = 2; +constexpr const char* kSlotSuffixes[kMaxNumSlots] = {"_a", "_b"}; +constexpr const char* kFakeDevicePath = "/fake/dev/path/"; +constexpr const char* kFakeDmDevicePath = "/fake/dm/dev/path/"; +constexpr const uint32_t kFakeMetadataSize = 65536; +constexpr const char* kDefaultGroup = "foo"; +constexpr const char* kFakeSuper = "fake_super"; + +// A map describing the size of each partition. +// "{name, size}" +using PartitionSizes = std::map<std::string, uint64_t>; + +// "{name_a, size}" +using PartitionSuffixSizes = std::map<std::string, uint64_t>; + +constexpr uint64_t kDefaultGroupSize = 5_GiB; +// Super device size. 1 MiB for metadata. +constexpr uint64_t kDefaultSuperSize = kDefaultGroupSize * 2 + 1_MiB; + +template <typename U, typename V> +inline std::ostream& operator<<(std::ostream& os, const std::map<U, V>& param) { + os << "{"; + bool first = true; + for (const auto& pair : param) { + if (!first) + os << ", "; + os << pair.first << ":" << pair.second; + first = false; + } + return os << "}"; +} + +template <typename V> +inline void VectorToStream(std::ostream& os, const V& param) { + os << "["; + bool first = true; + for (const auto& e : param) { + if (!first) + os << ", "; + os << e; + first = false; + } + os << "]"; +} + +inline std::ostream& operator<<(std::ostream& os, const PartitionUpdate& p) { + return os << "{" << p.partition_name() << ", " + << p.new_partition_info().size() << "}"; +} + +inline std::ostream& operator<<(std::ostream& os, + const DynamicPartitionGroup& g) { + os << "{" << g.name() << ", " << g.size() << ", "; + VectorToStream(os, g.partition_names()); + return os << "}"; +} + +inline std::ostream& operator<<(std::ostream& os, + const DeltaArchiveManifest& m) { + os << "{.groups = "; + VectorToStream(os, m.dynamic_partition_metadata().groups()); + os << ", .partitions = "; + VectorToStream(os, m.partitions()); + return os; +} + +inline std::string GetDevice(const std::string& name) { + return kFakeDevicePath + name; +} + +inline std::string GetDmDevice(const std::string& name) { + return kFakeDmDevicePath + name; +} + +inline DynamicPartitionGroup* AddGroup(DeltaArchiveManifest* manifest, + const std::string& group, + uint64_t group_size) { + auto* g = manifest->mutable_dynamic_partition_metadata()->add_groups(); + g->set_name(group); + g->set_size(group_size); + return g; +} + +inline void AddPartition(DeltaArchiveManifest* manifest, + DynamicPartitionGroup* group, + const std::string& partition, + uint64_t partition_size) { + group->add_partition_names(partition); + auto* p = manifest->add_partitions(); + p->set_partition_name(partition); + p->mutable_new_partition_info()->set_size(partition_size); +} + +// To support legacy tests, auto-convert {name_a: size} map to +// DeltaArchiveManifest. +inline DeltaArchiveManifest PartitionSuffixSizesToManifest( + const PartitionSuffixSizes& partition_sizes) { + DeltaArchiveManifest manifest; + for (const char* suffix : kSlotSuffixes) { + AddGroup(&manifest, std::string(kDefaultGroup) + suffix, kDefaultGroupSize); + } + for (const auto& pair : partition_sizes) { + for (size_t suffix_idx = 0; suffix_idx < kMaxNumSlots; ++suffix_idx) { + if (base::EndsWith(pair.first, + kSlotSuffixes[suffix_idx], + base::CompareCase::SENSITIVE)) { + AddPartition( + &manifest, + manifest.mutable_dynamic_partition_metadata()->mutable_groups( + suffix_idx), + pair.first, + pair.second); + } + } + } + return manifest; +} + +// To support legacy tests, auto-convert {name: size} map to PartitionMetadata. +inline DeltaArchiveManifest PartitionSizesToManifest( + const PartitionSizes& partition_sizes) { + DeltaArchiveManifest manifest; + auto* g = AddGroup(&manifest, std::string(kDefaultGroup), kDefaultGroupSize); + for (const auto& pair : partition_sizes) { + AddPartition(&manifest, g, pair.first, pair.second); + } + return manifest; +} + +inline std::unique_ptr<MetadataBuilder> NewFakeMetadata( + const DeltaArchiveManifest& manifest, + uint32_t partition_attr = 0, + uint64_t super_size = kDefaultSuperSize) { + auto builder = + MetadataBuilder::New(super_size, kFakeMetadataSize, kMaxNumSlots); + for (const auto& group : manifest.dynamic_partition_metadata().groups()) { + EXPECT_TRUE(builder->AddGroup(group.name(), group.size())); + for (const auto& partition_name : group.partition_names()) { + EXPECT_NE( + nullptr, + builder->AddPartition(partition_name, group.name(), partition_attr)); + } + } + for (const auto& partition : manifest.partitions()) { + auto p = builder->FindPartition(partition.partition_name()); + EXPECT_TRUE(p && builder->ResizePartition( + p, partition.new_partition_info().size())); + } + return builder; +} + +class MetadataMatcher : public MatcherInterface<MetadataBuilder*> { + public: + explicit MetadataMatcher(const PartitionSuffixSizes& partition_sizes) + : manifest_(PartitionSuffixSizesToManifest(partition_sizes)) {} + explicit MetadataMatcher(const DeltaArchiveManifest& manifest) + : manifest_(manifest) {} + + bool MatchAndExplain(MetadataBuilder* metadata, + MatchResultListener* listener) const override { + bool success = true; + for (const auto& group : manifest_.dynamic_partition_metadata().groups()) { + for (const auto& partition_name : group.partition_names()) { + auto p = metadata->FindPartition(partition_name); + if (p == nullptr) { + if (!success) + *listener << "; "; + *listener << "No partition " << partition_name; + success = false; + continue; + } + const auto& partition_updates = manifest_.partitions(); + auto it = std::find_if(partition_updates.begin(), + partition_updates.end(), + [&](const auto& p) { + return p.partition_name() == partition_name; + }); + if (it == partition_updates.end()) { + *listener << "Can't find partition update " << partition_name; + success = false; + continue; + } + auto partition_size = it->new_partition_info().size(); + if (p->size() != partition_size) { + if (!success) + *listener << "; "; + *listener << "Partition " << partition_name << " has size " + << p->size() << ", expected " << partition_size; + success = false; + } + if (p->group_name() != group.name()) { + if (!success) + *listener << "; "; + *listener << "Partition " << partition_name << " has group " + << p->group_name() << ", expected " << group.name(); + success = false; + } + } + } + return success; + } + + void DescribeTo(std::ostream* os) const override { + *os << "expect: " << manifest_; + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "expect not: " << manifest_; + } + + private: + DeltaArchiveManifest manifest_; +}; + +inline Matcher<MetadataBuilder*> MetadataMatches( + const PartitionSuffixSizes& partition_sizes) { + return MakeMatcher(new MetadataMatcher(partition_sizes)); +} + +inline Matcher<MetadataBuilder*> MetadataMatches( + const DeltaArchiveManifest& manifest) { + return MakeMatcher(new MetadataMatcher(manifest)); +} + +MATCHER_P(HasGroup, group, " has group " + group) { + auto groups = arg->ListGroups(); + return std::find(groups.begin(), groups.end(), group) != groups.end(); +} + +struct TestParam { + uint32_t source; + uint32_t target; +}; +inline std::ostream& operator<<(std::ostream& os, const TestParam& param) { + return os << "{source: " << param.source << ", target:" << param.target + << "}"; +} + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_DYNAMIC_PARTITION_TEST_UTILS_H_ diff --git a/dynamic_partition_utils.cc b/dynamic_partition_utils.cc new file mode 100644 index 00000000..f9bd886b --- /dev/null +++ b/dynamic_partition_utils.cc @@ -0,0 +1,39 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/dynamic_partition_utils.h" + +#include <vector> + +#include <base/logging.h> +#include <base/strings/string_util.h> + +using android::fs_mgr::MetadataBuilder; + +namespace chromeos_update_engine { + +void DeleteGroupsWithSuffix(MetadataBuilder* builder, + const std::string& suffix) { + std::vector<std::string> groups = builder->ListGroups(); + for (const auto& group_name : groups) { + if (base::EndsWith(group_name, suffix, base::CompareCase::SENSITIVE)) { + LOG(INFO) << "Removing group " << group_name; + builder->RemoveGroupAndPartitions(group_name); + } + } +} + +} // namespace chromeos_update_engine diff --git a/dynamic_partition_utils.h b/dynamic_partition_utils.h new file mode 100644 index 00000000..09fce00c --- /dev/null +++ b/dynamic_partition_utils.h @@ -0,0 +1,33 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_DYNAMIC_PARTITION_UTILS_H_ +#define UPDATE_ENGINE_DYNAMIC_PARTITION_UTILS_H_ + +#include <string> + +#include <liblp/builder.h> + +namespace chromeos_update_engine { + +// Delete all groups (and their partitions) in |builder| that have names +// ending with |suffix|. +void DeleteGroupsWithSuffix(android::fs_mgr::MetadataBuilder* builder, + const std::string& suffix); + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_DYNAMIC_PARTITION_UTILS_H_ diff --git a/excluder_chromeos_unittest.cc b/excluder_chromeos_unittest.cc index a8c14b39..dba77e47 100644 --- a/excluder_chromeos_unittest.cc +++ b/excluder_chromeos_unittest.cc @@ -29,7 +29,7 @@ using std::unique_ptr; namespace chromeos_update_engine { -constexpr char kDummyHash[] = +constexpr char kFakeHash[] = "71ff43d76e2488e394e46872f5b066cc25e394c2c3e3790dd319517883b33db1"; class ExcluderChromeOSTest : public ::testing::Test { @@ -47,20 +47,20 @@ class ExcluderChromeOSTest : public ::testing::Test { }; TEST_F(ExcluderChromeOSTest, ExclusionCheck) { - EXPECT_FALSE(excluder_->IsExcluded(kDummyHash)); - EXPECT_TRUE(excluder_->Exclude(kDummyHash)); - EXPECT_TRUE(excluder_->IsExcluded(kDummyHash)); + EXPECT_FALSE(excluder_->IsExcluded(kFakeHash)); + EXPECT_TRUE(excluder_->Exclude(kFakeHash)); + EXPECT_TRUE(excluder_->IsExcluded(kFakeHash)); } TEST_F(ExcluderChromeOSTest, ResetFlow) { EXPECT_TRUE(excluder_->Exclude("abc")); - EXPECT_TRUE(excluder_->Exclude(kDummyHash)); + EXPECT_TRUE(excluder_->Exclude(kFakeHash)); EXPECT_TRUE(excluder_->IsExcluded("abc")); - EXPECT_TRUE(excluder_->IsExcluded(kDummyHash)); + EXPECT_TRUE(excluder_->IsExcluded(kFakeHash)); EXPECT_TRUE(excluder_->Reset()); EXPECT_FALSE(excluder_->IsExcluded("abc")); - EXPECT_FALSE(excluder_->IsExcluded(kDummyHash)); + EXPECT_FALSE(excluder_->IsExcluded(kFakeHash)); } } // namespace chromeos_update_engine diff --git a/hardware_android.cc b/hardware_android.cc index 82f1b9a6..48945224 100644 --- a/hardware_android.cc +++ b/hardware_android.cc @@ -17,19 +17,29 @@ #include "update_engine/hardware_android.h" #include <sys/types.h> +#include <sys/utsname.h> #include <memory> +#include <string> +#include <string_view> +#include <android-base/parseint.h> #include <android-base/properties.h> #include <base/files/file_util.h> #include <bootloader_message/bootloader_message.h> +#include <kver/kernel_release.h> +#include <kver/utils.h> +#include "update_engine/common/error_code_utils.h" #include "update_engine/common/hardware.h" #include "update_engine/common/platform_constants.h" +#include "update_engine/common/utils.h" using android::base::GetBoolProperty; using android::base::GetIntProperty; using android::base::GetProperty; +using android::kver::IsKernelUpdateValid; +using android::kver::KernelRelease; using std::string; namespace chromeos_update_engine { @@ -46,6 +56,11 @@ const char kPropBootHardwareSKU[] = "ro.boot.hardware.sku"; const char kPropBootRevision[] = "ro.boot.revision"; const char kPropBuildDateUTC[] = "ro.build.date.utc"; +string GetPartitionBuildDate(const string& partition_name) { + return android::base::GetProperty("ro." + partition_name + ".build.date.utc", + ""); +} + } // namespace namespace hardware { @@ -181,7 +196,7 @@ bool HardwareAndroid::CancelPowerwash() { bool HardwareAndroid::GetNonVolatileDirectory(base::FilePath* path) const { base::FilePath local_path(constants::kNonVolatileDirectory); - if (!base::PathExists(local_path)) { + if (!base::DirectoryExists(local_path)) { LOG(ERROR) << "Non-volatile directory not found: " << local_path.value(); return false; } @@ -198,6 +213,13 @@ int64_t HardwareAndroid::GetBuildTimestamp() const { return GetIntProperty<int64_t>(kPropBuildDateUTC, 0); } +// Returns true if the device runs an userdebug build, and explicitly allows OTA +// downgrade. +bool HardwareAndroid::AllowDowngrade() const { + return GetBoolProperty("ro.ota.allow_downgrade", false) && + GetBoolProperty("ro.debuggable", false); +} + bool HardwareAndroid::GetFirstActiveOmahaPingSent() const { LOG(WARNING) << "STUB: Assuming first active omaha was never set."; return false; @@ -209,4 +231,75 @@ bool HardwareAndroid::SetFirstActiveOmahaPingSent() { return true; } +void HardwareAndroid::SetWarmReset(bool warm_reset) { + constexpr char warm_reset_prop[] = "ota.warm_reset"; + if (!android::base::SetProperty(warm_reset_prop, warm_reset ? "1" : "0")) { + LOG(WARNING) << "Failed to set prop " << warm_reset_prop; + } +} + +string HardwareAndroid::GetVersionForLogging( + const string& partition_name) const { + if (partition_name == "boot") { + struct utsname buf; + if (uname(&buf) != 0) { + PLOG(ERROR) << "Unable to call uname()"; + return ""; + } + auto kernel_release = + KernelRelease::Parse(buf.release, true /* allow_suffix */); + return kernel_release.has_value() ? kernel_release->string() : ""; + } + return GetPartitionBuildDate(partition_name); +} + +ErrorCode HardwareAndroid::IsPartitionUpdateValid( + const string& partition_name, const string& new_version) const { + if (partition_name == "boot") { + struct utsname buf; + if (uname(&buf) != 0) { + PLOG(ERROR) << "Unable to call uname()"; + return ErrorCode::kError; + } + return IsKernelUpdateValid(buf.release, new_version); + } + + const auto old_version = GetPartitionBuildDate(partition_name); + // TODO(zhangkelvin) for some partitions, missing a current timestamp should + // be an error, e.g. system, vendor, product etc. + auto error_code = utils::IsTimestampNewer(old_version, new_version); + if (error_code != ErrorCode::kSuccess) { + LOG(ERROR) << "Timestamp check failed with " + << utils::ErrorCodeToString(error_code) + << " Partition timestamp: " << old_version + << " Update timestamp: " << new_version; + } + return error_code; +} + +ErrorCode HardwareAndroid::IsKernelUpdateValid(const string& old_release, + const string& new_release) { + // Check that the package either contain an empty version (indicating that the + // new build does not use GKI), or a valid GKI kernel release. + std::optional<KernelRelease> new_kernel_release; + if (new_release.empty()) { + LOG(INFO) << "New build does not contain GKI."; + } else { + new_kernel_release = + KernelRelease::Parse(new_release, true /* allow_suffix */); + if (!new_kernel_release.has_value()) { + LOG(ERROR) << "New kernel release is not valid GKI kernel release: " + << new_release; + return ErrorCode::kDownloadManifestParseError; + } + } + + auto old_kernel_release = + KernelRelease::Parse(old_release, true /* allow_suffix */); + return android::kver::IsKernelUpdateValid(old_kernel_release, + new_kernel_release) + ? ErrorCode::kSuccess + : ErrorCode::kPayloadTimestampError; +} + } // namespace chromeos_update_engine diff --git a/hardware_android.h b/hardware_android.h index 6edf4683..b6704477 100644 --- a/hardware_android.h +++ b/hardware_android.h @@ -18,17 +18,20 @@ #define UPDATE_ENGINE_HARDWARE_ANDROID_H_ #include <string> +#include <string_view> #include <base/macros.h> #include <base/time/time.h> +#include <gtest/gtest_prod.h> +#include "update_engine/common/error_code.h" #include "update_engine/common/hardware.h" #include "update_engine/common/hardware_interface.h" namespace chromeos_update_engine { // Implements the real interface with the hardware in the Android platform. -class HardwareAndroid final : public HardwareInterface { +class HardwareAndroid : public HardwareInterface { public: HardwareAndroid() = default; ~HardwareAndroid() override = default; @@ -54,10 +57,23 @@ class HardwareAndroid final : public HardwareInterface { bool GetNonVolatileDirectory(base::FilePath* path) const override; bool GetPowerwashSafeDirectory(base::FilePath* path) const override; int64_t GetBuildTimestamp() const override; + bool AllowDowngrade() const override; bool GetFirstActiveOmahaPingSent() const override; bool SetFirstActiveOmahaPingSent() override; + void SetWarmReset(bool warm_reset) override; + [[nodiscard]] std::string GetVersionForLogging( + const std::string& partition_name) const override; + [[nodiscard]] ErrorCode IsPartitionUpdateValid( + const std::string& partition_name, + const std::string& new_version) const override; private: + FRIEND_TEST(HardwareAndroidTest, IsKernelUpdateValid); + + // Helper for IsPartitionUpdateValid. + static ErrorCode IsKernelUpdateValid(const std::string& old_release, + const std::string& new_release); + DISALLOW_COPY_AND_ASSIGN(HardwareAndroid); }; diff --git a/hardware_android_unittest.cc b/hardware_android_unittest.cc new file mode 100644 index 00000000..9a491f3c --- /dev/null +++ b/hardware_android_unittest.cc @@ -0,0 +1,67 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <gtest/gtest.h> + +#include "update_engine/common/error_code.h" +#include "update_engine/common/test_utils.h" +#include "update_engine/hardware_android.h" + +namespace chromeos_update_engine { + +TEST(HardwareAndroidTest, IsKernelUpdateValid) { + EXPECT_EQ(ErrorCode::kSuccess, + HardwareAndroid::IsKernelUpdateValid("5.4.42-not-gki", "")) + << "Legacy update should be fine"; + + EXPECT_EQ(ErrorCode::kSuccess, + HardwareAndroid::IsKernelUpdateValid("5.4.42-not-gki", + "5.4.42-android12-0")) + << "Update to GKI should be fine"; + + EXPECT_EQ( + ErrorCode::kDownloadManifestParseError, + HardwareAndroid::IsKernelUpdateValid("5.4.42-not-gki", "5.4.42-not-gki")) + << "Should report parse error for invalid version field"; + + EXPECT_EQ(ErrorCode::kSuccess, + HardwareAndroid::IsKernelUpdateValid( + "5.4.42-android12-0-something", "5.4.42-android12-0-something")) + << "Self update should be fine"; + + EXPECT_EQ(ErrorCode::kSuccess, + HardwareAndroid::IsKernelUpdateValid( + "5.4.42-android12-0-something", "5.4.43-android12-0-something")) + << "Sub-level update should be fine"; + + EXPECT_EQ( + ErrorCode::kSuccess, + HardwareAndroid::IsKernelUpdateValid("5.4.42-android12-0-something", + "5.10.10-android12-0-something")) + << "KMI version update should be fine"; + + EXPECT_EQ(ErrorCode::kPayloadTimestampError, + HardwareAndroid::IsKernelUpdateValid("5.4.42-android12-0-something", + "5.4.5-android12-0-something")) + << "Should detect sub-level downgrade"; + + EXPECT_EQ(ErrorCode::kPayloadTimestampError, + HardwareAndroid::IsKernelUpdateValid("5.4.42-android12-0-something", + "5.1.5-android12-0-something")) + << "Should detect KMI version downgrade"; +} + +} // namespace chromeos_update_engine diff --git a/hardware_chromeos.cc b/hardware_chromeos.cc index 2aae9f05..cce5e842 100644 --- a/hardware_chromeos.cc +++ b/hardware_chromeos.cc @@ -366,4 +366,18 @@ bool HardwareChromeOS::SetFirstActiveOmahaPingSent() { return true; } +void HardwareChromeOS::SetWarmReset(bool warm_reset) {} + +std::string HardwareChromeOS::GetVersionForLogging( + const std::string& partition_name) const { + // TODO(zhangkelvin) Implement per-partition timestamp for Chrome OS. + return ""; +} + +ErrorCode HardwareChromeOS::IsPartitionUpdateValid( + const std::string& partition_name, const std::string& new_version) const { + // TODO(zhangkelvin) Implement per-partition timestamp for Chrome OS. + return ErrorCode::kSuccess; +} + } // namespace chromeos_update_engine diff --git a/hardware_chromeos.h b/hardware_chromeos.h index 230e864d..bbfe2739 100644 --- a/hardware_chromeos.h +++ b/hardware_chromeos.h @@ -25,6 +25,7 @@ #include <base/time/time.h> #include <debugd/dbus-proxies.h> +#include "update_engine/common/error_code.h" #include "update_engine/common/hardware_interface.h" namespace chromeos_update_engine { @@ -59,8 +60,15 @@ class HardwareChromeOS final : public HardwareInterface { bool GetNonVolatileDirectory(base::FilePath* path) const override; bool GetPowerwashSafeDirectory(base::FilePath* path) const override; int64_t GetBuildTimestamp() const override; + bool AllowDowngrade() const override { return false; } bool GetFirstActiveOmahaPingSent() const override; bool SetFirstActiveOmahaPingSent() override; + void SetWarmReset(bool warm_reset) override; + std::string GetVersionForLogging( + const std::string& partition_name) const override; + ErrorCode IsPartitionUpdateValid( + const std::string& partition_name, + const std::string& new_version) const override; private: friend class HardwareChromeOSTest; diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc index d317d489..bce09209 100644 --- a/libcurl_http_fetcher.cc +++ b/libcurl_http_fetcher.cc @@ -31,6 +31,7 @@ #include <base/strings/string_split.h> #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> +#include <base/threading/thread_task_runner_handle.h> #ifdef __ANDROID__ #include <cutils/qtaguid.h> @@ -78,6 +79,7 @@ int LibcurlHttpFetcher::LibcurlCloseSocketCallback(void* clientp, #ifdef __ANDROID__ qtaguid_untagSocket(item); #endif // __ANDROID__ + LibcurlHttpFetcher* fetcher = static_cast<LibcurlHttpFetcher*>(clientp); // Stop watching the socket before closing it. for (size_t t = 0; t < base::size(fetcher->fd_controller_maps_); ++t) { @@ -456,6 +458,19 @@ void LibcurlHttpFetcher::CurlPerformOnce() { // There's either more work to do or we are paused, so we just keep the // file descriptors to watch up to date and exit, until we are done with the // work and we are not paused. +#ifdef __ANDROID__ + // When there's no base::SingleThreadTaskRunner on current thread, it's not + // possible to watch file descriptors. Just poll it later. This usually + // happens if brillo::FakeMessageLoop is used. + if (!base::ThreadTaskRunnerHandle::IsSet()) { + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&LibcurlHttpFetcher::CurlPerformOnce, + base::Unretained(this)), + TimeDelta::FromSeconds(1)); + return; + } +#endif SetupMessageLoopSources(); return; } diff --git a/libcurl_http_fetcher.h b/libcurl_http_fetcher.h index 97a9a87f..4e91b693 100644 --- a/libcurl_http_fetcher.h +++ b/libcurl_http_fetcher.h @@ -218,7 +218,7 @@ class LibcurlHttpFetcher : public HttpFetcher { } // Cleans up the following if they are non-null: - // curl(m) handles, fd_controller_maps_, timeout_id_. + // curl(m) handles, fd_controller_maps_(fd_task_maps_), timeout_id_. void CleanUp(); // Force terminate the transfer. This will invoke the delegate's (if any) diff --git a/libcurl_http_fetcher_unittest.cc b/libcurl_http_fetcher_unittest.cc index 20f05b9c..874ef2e9 100644 --- a/libcurl_http_fetcher_unittest.cc +++ b/libcurl_http_fetcher_unittest.cc @@ -100,8 +100,18 @@ TEST_F(LibcurlHttpFetcherTest, CouldNotResolveHostTest) { libcurl_fetcher_.BeginTransfer("https://An-uNres0lvable-uRl.invalid"); +#ifdef __ANDROID__ + // It's slower on Android that libcurl handle may not finish within 1 cycle. + // Will need to wait for more cycles until it finishes. Original test didn't + // correctly handle when we need to re-watch libcurl fds. + while (loop_.PendingTasks() && + libcurl_fetcher_.GetAuxiliaryErrorCode() == ErrorCode::kSuccess) { + loop_.RunOnce(true); + } +#else // The first time it can't resolve. loop_.RunOnce(true); +#endif EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(), ErrorCode::kUnresolvedHostError); @@ -131,8 +141,18 @@ TEST_F(LibcurlHttpFetcherTest, HostResolvedTest) { // easier to mock the part that depends on internet connectivity. libcurl_fetcher_.BeginTransfer("https://An-uNres0lvable-uRl.invalid"); +#ifdef __ANDROID__ + // It's slower on Android that libcurl handle may not finish within 1 cycle. + // Will need to wait for more cycles until it finishes. Original test didn't + // correctly handle when we need to re-watch libcurl fds. + while (loop_.PendingTasks() && + libcurl_fetcher_.GetAuxiliaryErrorCode() == ErrorCode::kSuccess) { + loop_.RunOnce(true); + } +#else // The first time it can't resolve. loop_.RunOnce(true); +#endif EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(), ErrorCode::kUnresolvedHostError); @@ -145,9 +165,19 @@ TEST_F(LibcurlHttpFetcherTest, HostResolvedTest) { [this]() { libcurl_fetcher_.http_response_code_ = 0; })); libcurl_fetcher_.transfer_size_ = 10; +#ifdef __ANDROID__ + // It's slower on Android that libcurl handle may not finish within 1 cycle. + // Will need to wait for more cycles until it finishes. Original test didn't + // correctly handle when we need to re-watch libcurl fds. + while (loop_.PendingTasks() && libcurl_fetcher_.GetAuxiliaryErrorCode() == + ErrorCode::kUnresolvedHostError) { + loop_.RunOnce(true); + } +#else // This time the host is resolved. But after that again we can't resolve // anymore (See above). loop_.RunOnce(true); +#endif EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(), ErrorCode::kUnresolvedHostRecovered); diff --git a/logging.cc b/logging.cc new file mode 100644 index 00000000..6320e368 --- /dev/null +++ b/logging.cc @@ -0,0 +1,87 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> + +#include <base/files/file_util.h> +#include <base/logging.h> +#include <base/strings/string_util.h> +#include <base/strings/stringprintf.h> + +#include "update_engine/common/utils.h" +#include "update_engine/logging.h" + +using std::string; + +namespace chromeos_update_engine { + +namespace { + +constexpr char kSystemLogsRoot[] = "/var/log"; + +void SetupLogSymlink(const string& symlink_path, const string& log_path) { + // TODO(petkov): To ensure a smooth transition between non-timestamped and + // timestamped logs, move an existing log to start the first timestamped + // one. This code can go away once all clients are switched to this version or + // we stop caring about the old-style logs. + if (utils::FileExists(symlink_path.c_str()) && + !utils::IsSymlink(symlink_path.c_str())) { + base::ReplaceFile( + base::FilePath(symlink_path), base::FilePath(log_path), nullptr); + } + base::DeleteFile(base::FilePath(symlink_path), true); + if (symlink(log_path.c_str(), symlink_path.c_str()) == -1) { + PLOG(ERROR) << "Unable to create symlink " << symlink_path + << " pointing at " << log_path; + } +} + +string SetupLogFile(const string& kLogsRoot) { + const string kLogSymlink = kLogsRoot + "/update_engine.log"; + const string kLogsDir = kLogsRoot + "/update_engine"; + const string kLogPath = + base::StringPrintf("%s/update_engine.%s", + kLogsDir.c_str(), + utils::GetTimeAsString(::time(nullptr)).c_str()); + mkdir(kLogsDir.c_str(), 0755); + SetupLogSymlink(kLogSymlink, kLogPath); + return kLogSymlink; +} + +} // namespace + +void SetupLogging(bool log_to_system, bool log_to_file) { + logging::LoggingSettings log_settings; + log_settings.lock_log = logging::DONT_LOCK_LOG_FILE; + log_settings.logging_dest = static_cast<logging::LoggingDestination>( + (log_to_system ? logging::LOG_TO_SYSTEM_DEBUG_LOG : 0) | + (log_to_file ? logging::LOG_TO_FILE : 0)); + log_settings.log_file = nullptr; + + string log_file; + if (log_to_file) { + log_file = SetupLogFile(kSystemLogsRoot); + log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE; + log_settings.log_file = log_file.c_str(); + } + logging::InitLogging(log_settings); +} + +} // namespace chromeos_update_engine diff --git a/logging.h b/logging.h new file mode 100644 index 00000000..c9e7483c --- /dev/null +++ b/logging.h @@ -0,0 +1,23 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace chromeos_update_engine { + +// Set up logging. |log_to_system| and |log_to_file| specifies +// the destination of logs. +void SetupLogging(bool log_to_system, bool log_to_file); + +} // namespace chromeos_update_engine diff --git a/logging_android.cc b/logging_android.cc new file mode 100644 index 00000000..0219075c --- /dev/null +++ b/logging_android.cc @@ -0,0 +1,276 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <inttypes.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <algorithm> +#include <functional> +#include <iomanip> +#include <string> +#include <string_view> +#include <vector> + +#include <android-base/file.h> +#include <android-base/strings.h> +#include <android-base/unique_fd.h> +#include <base/files/dir_reader_posix.h> +#include <base/logging.h> +#include <base/strings/string_util.h> +#include <base/strings/stringprintf.h> +#include <log/log.h> + +#include "update_engine/common/utils.h" + +using std::string; + +#ifdef _UE_SIDELOAD +constexpr bool kSideload = true; +#else +constexpr bool kSideload = false; +#endif + +namespace chromeos_update_engine { +namespace { + +constexpr char kSystemLogsRoot[] = "/data/misc/update_engine_log"; +constexpr size_t kLogCount = 5; + +// Keep the most recent |kLogCount| logs but remove the old ones in +// "/data/misc/update_engine_log/". +void DeleteOldLogs(const string& kLogsRoot) { + base::DirReaderPosix reader(kLogsRoot.c_str()); + if (!reader.IsValid()) { + LOG(ERROR) << "Failed to read " << kLogsRoot; + return; + } + + std::vector<string> old_logs; + while (reader.Next()) { + if (reader.name()[0] == '.') + continue; + + // Log files are in format "update_engine.%Y%m%d-%H%M%S", + // e.g. update_engine.20090103-231425 + uint64_t date; + uint64_t local_time; + if (sscanf(reader.name(), + "update_engine.%" PRIu64 "-%" PRIu64 "", + &date, + &local_time) == 2) { + old_logs.push_back(reader.name()); + } else { + LOG(WARNING) << "Unrecognized log file " << reader.name(); + } + } + + std::sort(old_logs.begin(), old_logs.end(), std::greater<string>()); + for (size_t i = kLogCount; i < old_logs.size(); i++) { + string log_path = kLogsRoot + "/" + old_logs[i]; + if (unlink(log_path.c_str()) == -1) { + PLOG(WARNING) << "Failed to unlink " << log_path; + } + } +} + +string SetupLogFile(const string& kLogsRoot) { + DeleteOldLogs(kLogsRoot); + + return base::StringPrintf("%s/update_engine.%s", + kLogsRoot.c_str(), + utils::GetTimeAsString(::time(nullptr)).c_str()); +} + +const char* LogPriorityToCString(int priority) { + switch (priority) { + case ANDROID_LOG_VERBOSE: + return "VERBOSE"; + case ANDROID_LOG_DEBUG: + return "DEBUG"; + case ANDROID_LOG_INFO: + return "INFO"; + case ANDROID_LOG_WARN: + return "WARN"; + case ANDROID_LOG_ERROR: + return "ERROR"; + case ANDROID_LOG_FATAL: + return "FATAL"; + default: + return "UNKNOWN"; + } +} + +using LoggerFunction = std::function<void(const struct __android_log_message*)>; + +class FileLogger { + public: + explicit FileLogger(const string& path) { + fd_.reset(TEMP_FAILURE_RETRY( + open(path.c_str(), + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW | O_SYNC, + 0644))); + if (fd_ == -1) { + // Use ALOGE that logs to logd before __android_log_set_logger. + ALOGE("Cannot open persistent log %s: %s", path.c_str(), strerror(errno)); + return; + } + // The log file will have AID_LOG as group ID; this GID is inherited from + // the parent directory "/data/misc/update_engine_log" which sets the SGID + // bit. + if (fchmod(fd_.get(), 0640) == -1) { + // Use ALOGE that logs to logd before __android_log_set_logger. + ALOGE("Cannot chmod 0640 persistent log %s: %s", + path.c_str(), + strerror(errno)); + return; + } + } + // Copy-constructor needed to be converted to std::function. + FileLogger(const FileLogger& other) { fd_.reset(dup(other.fd_)); } + void operator()(const struct __android_log_message* log_message) { + if (fd_ == -1) { + return; + } + + std::string_view message_str = + log_message->message != nullptr ? log_message->message : ""; + + WriteToFd(GetPrefix(log_message)); + WriteToFd(message_str); + WriteToFd("\n"); + } + + private: + android::base::unique_fd fd_; + void WriteToFd(std::string_view message) { + ignore_result( + android::base::WriteFully(fd_, message.data(), message.size())); + } + + string GetPrefix(const struct __android_log_message* log_message) { + std::stringstream ss; + timeval tv; + gettimeofday(&tv, nullptr); + time_t t = tv.tv_sec; + struct tm local_time; + localtime_r(&t, &local_time); + struct tm* tm_time = &local_time; + ss << "[" << std::setfill('0') << std::setw(2) << 1 + tm_time->tm_mon + << std::setw(2) << tm_time->tm_mday << '/' << std::setw(2) + << tm_time->tm_hour << std::setw(2) << tm_time->tm_min << std::setw(2) + << tm_time->tm_sec << '.' << std::setw(6) << tv.tv_usec << "] "; + // libchrome logs prepends |message| with severity, file and line, but + // leave logger_data->file as nullptr. + // libbase / liblog logs doesn't. Hence, add them to match the style. + // For liblog logs that doesn't set logger_data->file, not printing the + // priority is acceptable. + if (log_message->file) { + ss << "[" << LogPriorityToCString(log_message->priority) << ':' + << log_message->file << '(' << log_message->line << ")] "; + } + return ss.str(); + } +}; + +class CombinedLogger { + public: + CombinedLogger(bool log_to_system, bool log_to_file) { + if (log_to_system) { + if (kSideload) { + // No logd in sideload. Use stdout. + // recovery has already redirected stdio properly. + loggers_.push_back(__android_log_stderr_logger); + } else { + loggers_.push_back(__android_log_logd_logger); + } + } + if (log_to_file) { + loggers_.push_back(std::move(FileLogger(SetupLogFile(kSystemLogsRoot)))); + } + } + void operator()(const struct __android_log_message* log_message) { + for (auto&& logger : loggers_) { + logger(log_message); + } + } + + private: + std::vector<LoggerFunction> loggers_; +}; + +// Redirect all libchrome logs to liblog using our custom handler that does +// not call __android_log_write and explicitly write to stderr at the same +// time. The preset CombinedLogger already writes to stderr properly. +bool RedirectToLiblog(int severity, + const char* file, + int line, + size_t message_start, + const std::string& str_newline) { + android_LogPriority priority = + (severity < 0) ? ANDROID_LOG_VERBOSE : ANDROID_LOG_UNKNOWN; + switch (severity) { + case logging::LOG_INFO: + priority = ANDROID_LOG_INFO; + break; + case logging::LOG_WARNING: + priority = ANDROID_LOG_WARN; + break; + case logging::LOG_ERROR: + priority = ANDROID_LOG_ERROR; + break; + case logging::LOG_FATAL: + priority = ANDROID_LOG_FATAL; + break; + } + std::string_view sv = str_newline; + ignore_result(android::base::ConsumeSuffix(&sv, "\n")); + std::string str(sv.data(), sv.size()); + // This will eventually be redirected to CombinedLogger. + // Use nullptr as tag so that liblog infers log tag from getprogname(). + __android_log_write(priority, nullptr /* tag */, str.c_str()); + return true; +} + +} // namespace + +void SetupLogging(bool log_to_system, bool log_to_file) { + // Note that libchrome logging uses liblog. + // By calling liblog's __android_log_set_logger function, all of libchrome + // (used by update_engine) / libbase / liblog (used by depended modules) + // logging eventually redirects to CombinedLogger. + static auto g_logger = + std::make_unique<CombinedLogger>(log_to_system, log_to_file); + __android_log_set_logger([](const struct __android_log_message* log_message) { + (*g_logger)(log_message); + }); + + // libchrome logging should not log to file. + logging::LoggingSettings log_settings; + log_settings.lock_log = logging::DONT_LOCK_LOG_FILE; + log_settings.logging_dest = + static_cast<logging::LoggingDestination>(logging::LOG_NONE); + log_settings.log_file = nullptr; + logging::InitLogging(log_settings); + logging::SetLogItems(false /* enable_process_id */, + false /* enable_thread_id */, + false /* enable_timestamp */, + false /* enable_tickcount */); + logging::SetLogMessageHandler(&RedirectToLiblog); +} + +} // namespace chromeos_update_engine @@ -14,154 +14,23 @@ // limitations under the License. // -#include <inttypes.h> #include <sys/stat.h> #include <sys/types.h> -#include <unistd.h> #include <xz.h> -#include <algorithm> -#include <string> -#include <vector> - #include <base/at_exit.h> #include <base/command_line.h> -#include <base/files/dir_reader_posix.h> -#include <base/files/file_util.h> #include <base/logging.h> -#include <base/strings/string_util.h> -#include <base/strings/stringprintf.h> #include <brillo/flag_helper.h> #include "update_engine/common/subprocess.h" #include "update_engine/common/terminator.h" #include "update_engine/common/utils.h" #include "update_engine/daemon_base.h" +#include "update_engine/logging.h" using std::string; -namespace chromeos_update_engine { -namespace { - -string GetTimeAsString(time_t utime) { - struct tm tm; - CHECK_EQ(localtime_r(&utime, &tm), &tm); - char str[16]; - CHECK_EQ(strftime(str, sizeof(str), "%Y%m%d-%H%M%S", &tm), 15u); - return str; -} - -#ifdef __ANDROID__ -constexpr char kSystemLogsRoot[] = "/data/misc/update_engine_log"; -constexpr size_t kLogCount = 5; - -// Keep the most recent |kLogCount| logs but remove the old ones in -// "/data/misc/update_engine_log/". -void DeleteOldLogs(const string& kLogsRoot) { - base::DirReaderPosix reader(kLogsRoot.c_str()); - if (!reader.IsValid()) { - LOG(ERROR) << "Failed to read " << kLogsRoot; - return; - } - - std::vector<string> old_logs; - while (reader.Next()) { - if (reader.name()[0] == '.') - continue; - - // Log files are in format "update_engine.%Y%m%d-%H%M%S", - // e.g. update_engine.20090103-231425 - uint64_t date; - uint64_t local_time; - if (sscanf(reader.name(), - "update_engine.%" PRIu64 "-%" PRIu64 "", - &date, - &local_time) == 2) { - old_logs.push_back(reader.name()); - } else { - LOG(WARNING) << "Unrecognized log file " << reader.name(); - } - } - - std::sort(old_logs.begin(), old_logs.end(), std::greater<string>()); - for (size_t i = kLogCount; i < old_logs.size(); i++) { - string log_path = kLogsRoot + "/" + old_logs[i]; - if (unlink(log_path.c_str()) == -1) { - PLOG(WARNING) << "Failed to unlink " << log_path; - } - } -} - -string SetupLogFile(const string& kLogsRoot) { - DeleteOldLogs(kLogsRoot); - - return base::StringPrintf("%s/update_engine.%s", - kLogsRoot.c_str(), - GetTimeAsString(::time(nullptr)).c_str()); -} -#else -constexpr char kSystemLogsRoot[] = "/var/log"; - -void SetupLogSymlink(const string& symlink_path, const string& log_path) { - // TODO(petkov): To ensure a smooth transition between non-timestamped and - // timestamped logs, move an existing log to start the first timestamped - // one. This code can go away once all clients are switched to this version or - // we stop caring about the old-style logs. - if (utils::FileExists(symlink_path.c_str()) && - !utils::IsSymlink(symlink_path.c_str())) { - base::ReplaceFile( - base::FilePath(symlink_path), base::FilePath(log_path), nullptr); - } - base::DeleteFile(base::FilePath(symlink_path), true); - if (symlink(log_path.c_str(), symlink_path.c_str()) == -1) { - PLOG(ERROR) << "Unable to create symlink " << symlink_path - << " pointing at " << log_path; - } -} - -string SetupLogFile(const string& kLogsRoot) { - const string kLogSymlink = kLogsRoot + "/update_engine.log"; - const string kLogsDir = kLogsRoot + "/update_engine"; - const string kLogPath = - base::StringPrintf("%s/update_engine.%s", - kLogsDir.c_str(), - GetTimeAsString(::time(nullptr)).c_str()); - mkdir(kLogsDir.c_str(), 0755); - SetupLogSymlink(kLogSymlink, kLogPath); - return kLogSymlink; -} -#endif // __ANDROID__ - -void SetupLogging(bool log_to_system, bool log_to_file) { - logging::LoggingSettings log_settings; - log_settings.lock_log = logging::DONT_LOCK_LOG_FILE; - log_settings.logging_dest = static_cast<logging::LoggingDestination>( - (log_to_system ? logging::LOG_TO_SYSTEM_DEBUG_LOG : 0) | - (log_to_file ? logging::LOG_TO_FILE : 0)); - log_settings.log_file = nullptr; - - string log_file; - if (log_to_file) { - log_file = SetupLogFile(kSystemLogsRoot); - log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE; -#if BASE_VER < 780000 - log_settings.log_file = log_file.c_str(); -#else - log_settings.log_file_path = log_file.c_str(); -#endif - } - logging::InitLogging(log_settings); - -#ifdef __ANDROID__ - // The log file will have AID_LOG as group ID; this GID is inherited from the - // parent directory "/data/misc/update_engine_log" which sets the SGID bit. - chmod(log_file.c_str(), 0640); -#endif -} - -} // namespace -} // namespace chromeos_update_engine - int main(int argc, char** argv) { DEFINE_bool(logtofile, false, "Write logs to a file in log_dir."); DEFINE_bool(logtostderr, diff --git a/metrics_reporter_android.cc b/metrics_reporter_android.cc index 9165f0d5..d8fa6e5b 100644 --- a/metrics_reporter_android.cc +++ b/metrics_reporter_android.cc @@ -16,17 +16,37 @@ #include "update_engine/metrics_reporter_android.h" +#include <stdint.h> + #include <memory> #include <string> -#include <metricslogger/metrics_logger.h> +#include <android-base/properties.h> +#include <base/strings/string_util.h> +#include <fs_mgr.h> +#include <libdm/dm.h> +#include <liblp/builder.h> +#include <liblp/liblp.h> +#include <statslog.h> #include "update_engine/common/constants.h" +using android::fs_mgr::GetPartitionGroupName; +using android::fs_mgr::LpMetadata; +using android::fs_mgr::MetadataBuilder; +using android::fs_mgr::ReadMetadata; +using android::fs_mgr::SlotNumberForSlotSuffix; +using base::EndsWith; + namespace { -void LogHistogram(const std::string& metrics, int value) { - android::metricslogger::LogHistogram(metrics, value); - LOG(INFO) << "uploading " << value << " to histogram for metric " << metrics; +// A number offset adds on top of the enum value. e.g. ErrorCode::SUCCESS will +// be reported as 10000, and AttemptResult::UPDATE_CANCELED will be reported as +// 10011. This keeps the ordering of update engine's enum definition when statsd +// atoms reserve the value 0 for unknown state. +constexpr auto kMetricsReporterEnumOffset = 10000; + +int32_t GetStatsdEnumValue(int32_t value) { + return kMetricsReporterEnumOffset + value; } } // namespace @@ -34,41 +54,6 @@ namespace chromeos_update_engine { namespace metrics { -// The histograms are defined in: -// depot/google3/analysis/uma/configs/clearcut/TRON/histograms.xml -constexpr char kMetricsUpdateEngineAttemptNumber[] = - "ota_update_engine_attempt_number"; -constexpr char kMetricsUpdateEngineAttemptResult[] = - "ota_update_engine_attempt_result"; -constexpr char kMetricsUpdateEngineAttemptDurationInMinutes[] = - "ota_update_engine_attempt_fixed_duration_boottime_in_minutes"; -constexpr char kMetricsUpdateEngineAttemptDurationUptimeInMinutes[] = - "ota_update_engine_attempt_duration_monotonic_in_minutes"; -constexpr char kMetricsUpdateEngineAttemptErrorCode[] = - "ota_update_engine_attempt_error_code"; -constexpr char kMetricsUpdateEngineAttemptPayloadSizeMiB[] = - "ota_update_engine_attempt_payload_size_mib"; -constexpr char kMetricsUpdateEngineAttemptPayloadType[] = - "ota_update_engine_attempt_payload_type"; -constexpr char kMetricsUpdateEngineAttemptCurrentBytesDownloadedMiB[] = - "ota_update_engine_attempt_fixed_current_bytes_downloaded_mib"; - -constexpr char kMetricsUpdateEngineSuccessfulUpdateAttemptCount[] = - "ota_update_engine_successful_update_attempt_count"; -constexpr char kMetricsUpdateEngineSuccessfulUpdateTotalDurationInMinutes[] = - "ota_update_engine_successful_update_fixed_total_duration_in_minutes"; -constexpr char kMetricsUpdateEngineSuccessfulUpdatePayloadSizeMiB[] = - "ota_update_engine_successful_update_payload_size_mib"; -constexpr char kMetricsUpdateEngineSuccessfulUpdatePayloadType[] = - "ota_update_engine_successful_update_payload_type"; -constexpr char kMetricsUpdateEngineSuccessfulUpdateRebootCount[] = - "ota_update_engine_successful_update_reboot_count"; -constexpr char kMetricsUpdateEngineSuccessfulUpdateTotalBytesDownloadedMiB[] = - "ota_update_engine_successful_update_total_bytes_downloaded_mib"; -constexpr char - kMetricsUpdateEngineSuccessfulUpdateDownloadOverheadPercentage[] = - "ota_update_engine_successful_update_download_overhead_percentage"; - std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter() { return std::make_unique<MetricsReporterAndroid>(); } @@ -84,22 +69,56 @@ void MetricsReporterAndroid::ReportUpdateAttemptMetrics( int64_t payload_size, metrics::AttemptResult attempt_result, ErrorCode error_code) { - LogHistogram(metrics::kMetricsUpdateEngineAttemptNumber, attempt_number); - LogHistogram(metrics::kMetricsUpdateEngineAttemptPayloadType, - static_cast<int>(payload_type)); - LogHistogram(metrics::kMetricsUpdateEngineAttemptDurationInMinutes, - duration.InMinutes()); - LogHistogram(metrics::kMetricsUpdateEngineAttemptDurationUptimeInMinutes, - duration_uptime.InMinutes()); - int64_t payload_size_mib = payload_size / kNumBytesInOneMiB; - LogHistogram(metrics::kMetricsUpdateEngineAttemptPayloadSizeMiB, - payload_size_mib); - LogHistogram(metrics::kMetricsUpdateEngineAttemptResult, - static_cast<int>(attempt_result)); - LogHistogram(metrics::kMetricsUpdateEngineAttemptErrorCode, - static_cast<int>(error_code)); + int64_t super_partition_size_bytes = 0; + int64_t super_free_space = 0; + int64_t slot_size_bytes = 0; + + if (android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) { + uint32_t slot = SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix()); + auto super_device = fs_mgr_get_super_partition_name(); + std::unique_ptr<LpMetadata> metadata = ReadMetadata(super_device, slot); + if (metadata) { + super_partition_size_bytes = GetTotalSuperPartitionSize(*metadata); + + for (const auto& group : metadata->groups) { + if (EndsWith(GetPartitionGroupName(group), + fs_mgr_get_slot_suffix(), + base::CompareCase::SENSITIVE)) { + slot_size_bytes += group.maximum_size; + } + } + + auto metadata_builder = MetadataBuilder::New(*metadata); + if (metadata_builder) { + auto free_regions = metadata_builder->GetFreeRegions(); + for (const auto& interval : free_regions) { + super_free_space += interval.length(); + } + super_free_space *= android::dm::kSectorSize; + } else { + LOG(ERROR) << "Cannot create metadata builder."; + } + } else { + LOG(ERROR) << "Could not read dynamic partition metadata for device: " + << super_device; + } + } + + android::util::stats_write( + android::util::UPDATE_ENGINE_UPDATE_ATTEMPT_REPORTED, + attempt_number, + GetStatsdEnumValue(static_cast<int32_t>(payload_type)), + duration.InMinutes(), + duration_uptime.InMinutes(), + payload_size_mib, + GetStatsdEnumValue(static_cast<int32_t>(attempt_result)), + GetStatsdEnumValue(static_cast<int32_t>(error_code)), + android::base::GetProperty("ro.build.fingerprint", "").c_str(), + super_partition_size_bytes, + slot_size_bytes, + super_free_space); } void MetricsReporterAndroid::ReportUpdateAttemptDownloadMetrics( @@ -108,8 +127,9 @@ void MetricsReporterAndroid::ReportUpdateAttemptDownloadMetrics( DownloadSource /* download_source */, metrics::DownloadErrorCode /* payload_download_error_code */, metrics::ConnectionType /* connection_type */) { - LogHistogram(metrics::kMetricsUpdateEngineAttemptCurrentBytesDownloadedMiB, - payload_bytes_downloaded / kNumBytesInOneMiB); + // TODO(xunchang) add statsd reporting + LOG(INFO) << "Current update attempt downloads " + << payload_bytes_downloaded / kNumBytesInOneMiB << " bytes data"; } void MetricsReporterAndroid::ReportSuccessfulUpdateMetrics( @@ -123,37 +143,28 @@ void MetricsReporterAndroid::ReportSuccessfulUpdateMetrics( base::TimeDelta /* total_duration_uptime */, int reboot_count, int /* url_switch_count */) { - LogHistogram(metrics::kMetricsUpdateEngineSuccessfulUpdateAttemptCount, - attempt_count); - LogHistogram(metrics::kMetricsUpdateEngineSuccessfulUpdatePayloadType, - static_cast<int>(payload_type)); - int64_t payload_size_mib = payload_size / kNumBytesInOneMiB; - LogHistogram(metrics::kMetricsUpdateEngineSuccessfulUpdatePayloadSizeMiB, - payload_size_mib); - int64_t total_bytes_downloaded = 0; for (size_t i = 0; i < kNumDownloadSources; i++) { total_bytes_downloaded += num_bytes_downloaded[i] / kNumBytesInOneMiB; } - LogHistogram( - metrics::kMetricsUpdateEngineSuccessfulUpdateTotalBytesDownloadedMiB, - total_bytes_downloaded); - LogHistogram( - metrics::kMetricsUpdateEngineSuccessfulUpdateDownloadOverheadPercentage, - download_overhead_percentage); - - LogHistogram( - metrics::kMetricsUpdateEngineSuccessfulUpdateTotalDurationInMinutes, - total_duration.InMinutes()); - LogHistogram(metrics::kMetricsUpdateEngineSuccessfulUpdateRebootCount, - reboot_count); + + android::util::stats_write( + android::util::UPDATE_ENGINE_SUCCESSFUL_UPDATE_REPORTED, + static_cast<int32_t>(attempt_count), + GetStatsdEnumValue(static_cast<int32_t>(payload_type)), + static_cast<int32_t>(payload_size_mib), + static_cast<int32_t>(total_bytes_downloaded), + static_cast<int32_t>(download_overhead_percentage), + static_cast<int32_t>(total_duration.InMinutes()), + static_cast<int32_t>(reboot_count)); } void MetricsReporterAndroid::ReportAbnormallyTerminatedUpdateAttemptMetrics() { int attempt_result = static_cast<int>(metrics::AttemptResult::kAbnormalTermination); - LogHistogram(metrics::kMetricsUpdateEngineAttemptResult, attempt_result); + // TODO(xunchang) add statsd reporting + LOG(INFO) << "Abnormally terminated update attempt result " << attempt_result; } }; // namespace chromeos_update_engine diff --git a/metrics_utils.cc b/metrics_utils.cc index 9f0caa52..2211a67b 100644 --- a/metrics_utils.cc +++ b/metrics_utils.cc @@ -72,6 +72,8 @@ metrics::AttemptResult GetAttemptResult(ErrorCode code) { case ErrorCode::kFilesystemCopierError: case ErrorCode::kFilesystemVerifierError: case ErrorCode::kVerityCalculationError: + case ErrorCode::kNotEnoughSpace: + case ErrorCode::kDeviceCorrupted: return metrics::AttemptResult::kOperationExecutionError; case ErrorCode::kDownloadMetadataSignatureMismatch: @@ -237,6 +239,8 @@ metrics::DownloadErrorCode GetDownloadErrorCode(ErrorCode code) { case ErrorCode::kRollbackNotPossible: case ErrorCode::kFirstActiveOmahaPingSentPersistenceError: case ErrorCode::kVerityCalculationError: + case ErrorCode::kNotEnoughSpace: + case ErrorCode::kDeviceCorrupted: case ErrorCode::kPackageExcludedFromUpdate: break; diff --git a/mock_dynamic_partition_control.h b/mock_dynamic_partition_control.h index 24aca06d..e85df327 100644 --- a/mock_dynamic_partition_control.h +++ b/mock_dynamic_partition_control.h @@ -17,37 +17,102 @@ #include <stdint.h> #include <memory> +#include <set> #include <string> #include <gmock/gmock.h> -#include "update_engine/dynamic_partition_control_interface.h" +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/dynamic_partition_control_interface.h" +#include "update_engine/dynamic_partition_control_android.h" namespace chromeos_update_engine { -class MockDynamicPartitionControl : public DynamicPartitionControlInterface { +class MockDynamicPartitionControlAndroid + : public DynamicPartitionControlAndroid { public: - MOCK_METHOD5(MapPartitionOnDeviceMapper, - bool(const std::string&, - const std::string&, - uint32_t, - bool, - std::string*)); - MOCK_METHOD2(UnmapPartitionOnDeviceMapper, bool(const std::string&, bool)); - MOCK_METHOD0(Cleanup, void()); - MOCK_METHOD1(DeviceExists, bool(const std::string&)); - MOCK_METHOD1(GetState, ::android::dm::DmDeviceState(const std::string&)); - MOCK_METHOD2(GetDmDevicePathByName, bool(const std::string&, std::string*)); - MOCK_METHOD3(LoadMetadataBuilder, - std::unique_ptr<::android::fs_mgr::MetadataBuilder>( - const std::string&, uint32_t, uint32_t)); - MOCK_METHOD3(StoreMetadata, - bool(const std::string&, - android::fs_mgr::MetadataBuilder*, - uint32_t)); - MOCK_METHOD1(GetDeviceDir, bool(std::string*)); - MOCK_METHOD0(IsDynamicPartitionsEnabled, bool()); - MOCK_METHOD0(IsDynamicPartitionsRetrofit, bool()); + MOCK_METHOD( + bool, + MapPartitionOnDeviceMapper, + (const std::string&, const std::string&, uint32_t, bool, std::string*), + (override)); + MOCK_METHOD(bool, + UnmapPartitionOnDeviceMapper, + (const std::string&), + (override)); + MOCK_METHOD(void, Cleanup, (), (override)); + MOCK_METHOD(bool, DeviceExists, (const std::string&), (override)); + MOCK_METHOD(::android::dm::DmDeviceState, + GetState, + (const std::string&), + (override)); + MOCK_METHOD(bool, + GetDmDevicePathByName, + (const std::string&, std::string*), + (override)); + MOCK_METHOD(std::unique_ptr<::android::fs_mgr::MetadataBuilder>, + LoadMetadataBuilder, + (const std::string&, uint32_t), + (override)); + MOCK_METHOD(std::unique_ptr<::android::fs_mgr::MetadataBuilder>, + LoadMetadataBuilder, + (const std::string&, uint32_t, uint32_t), + (override)); + MOCK_METHOD(bool, + StoreMetadata, + (const std::string&, android::fs_mgr::MetadataBuilder*, uint32_t), + (override)); + MOCK_METHOD(bool, GetDeviceDir, (std::string*), (override)); + MOCK_METHOD(FeatureFlag, GetDynamicPartitionsFeatureFlag, (), (override)); + MOCK_METHOD(std::string, GetSuperPartitionName, (uint32_t), (override)); + MOCK_METHOD(FeatureFlag, GetVirtualAbFeatureFlag, (), (override)); + MOCK_METHOD(bool, FinishUpdate, (bool), (override)); + MOCK_METHOD(bool, + GetSystemOtherPath, + (uint32_t, uint32_t, const std::string&, std::string*, bool*), + (override)); + MOCK_METHOD(bool, + EraseSystemOtherAvbFooter, + (uint32_t, uint32_t), + (override)); + MOCK_METHOD(std::optional<bool>, IsAvbEnabledOnSystemOther, (), (override)); + MOCK_METHOD(bool, IsRecovery, (), (override)); + MOCK_METHOD(bool, + PrepareDynamicPartitionsForUpdate, + (uint32_t, uint32_t, const DeltaArchiveManifest&, bool), + (override)); + + void set_fake_mapped_devices(const std::set<std::string>& fake) override { + DynamicPartitionControlAndroid::set_fake_mapped_devices(fake); + } + + bool RealGetSystemOtherPath(uint32_t source_slot, + uint32_t target_slot, + const std::string& partition_name_suffix, + std::string* path, + bool* should_unmap) { + return DynamicPartitionControlAndroid::GetSystemOtherPath( + source_slot, target_slot, partition_name_suffix, path, should_unmap); + } + + bool RealEraseSystemOtherAvbFooter(uint32_t source_slot, + uint32_t target_slot) { + return DynamicPartitionControlAndroid::EraseSystemOtherAvbFooter( + source_slot, target_slot); + } + + std::optional<bool> RealIsAvbEnabledInFstab(const std::string& path) { + return DynamicPartitionControlAndroid::IsAvbEnabledInFstab(path); + } + + bool RealPrepareDynamicPartitionsForUpdate( + uint32_t source_slot, + uint32_t target_slot, + const DeltaArchiveManifest& manifest, + bool delete_source) { + return DynamicPartitionControlAndroid::PrepareDynamicPartitionsForUpdate( + source_slot, target_slot, manifest, delete_source); + } }; } // namespace chromeos_update_engine diff --git a/omaha_request_action.cc b/omaha_request_action.cc index 83ee5b22..161cf43c 100644 --- a/omaha_request_action.cc +++ b/omaha_request_action.cc @@ -351,7 +351,7 @@ int OmahaRequestAction::GetInstallDate(SystemState* system_state) { // If we have the value stored on disk, just return it. int64_t stored_value; if (prefs->GetInt64(kPrefsInstallDateDays, &stored_value)) { - // Convert and sanity-check. + // Convert and validity-check. int install_date_days = static_cast<int>(stored_value); if (install_date_days >= 0) return install_date_days; @@ -957,10 +957,10 @@ void OmahaRequestAction::TransferComplete(HttpFetcher* fetcher, int code = GetHTTPResponseCode(); LOG(ERROR) << "Omaha request network transfer failed with HTTPResponseCode=" << code; - // Makes sure we send sane error values. + // Makes sure we send proper error values. if (code < 0 || code >= 1000) { code = 999; - LOG(WARNING) << "Converting to sane HTTPResponseCode=" << code; + LOG(WARNING) << "Converting to proper HTTPResponseCode=" << code; } completer.set_code(static_cast<ErrorCode>( static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + code)); diff --git a/omaha_request_builder_xml.cc b/omaha_request_builder_xml.cc index 8add89fe..690a4eff 100644 --- a/omaha_request_builder_xml.cc +++ b/omaha_request_builder_xml.cc @@ -230,7 +230,7 @@ string OmahaRequestBuilderXml::GetCohortArg(const string arg_name, if (!prefs_->GetString(prefs_key, &cohort_value) || cohort_value.empty()) return ""; } - // This is a sanity check to avoid sending a huge XML file back to Ohama due + // This is a validity check to avoid sending a huge XML file back to Ohama due // to a compromised stateful partition making the update check fail in low // network environments envent after a reboot. if (cohort_value.size() > 1024) { diff --git a/omaha_request_builder_xml_unittest.cc b/omaha_request_builder_xml_unittest.cc index 291189d6..a8044205 100644 --- a/omaha_request_builder_xml_unittest.cc +++ b/omaha_request_builder_xml_unittest.cc @@ -368,7 +368,7 @@ TEST_F(OmahaRequestBuilderXmlTest, 1, CountSubstringInString( kRequestXml, - "<event eventtype=\"3\" eventresult=\"0\" errorcode=\"60\"></event>")) + "<event eventtype=\"3\" eventresult=\"0\" errorcode=\"62\"></event>")) << kRequestXml; } @@ -399,7 +399,7 @@ TEST_F(OmahaRequestBuilderXmlTest, 2, CountSubstringInString( kRequestXml, - "<event eventtype=\"3\" eventresult=\"0\" errorcode=\"60\"></event>")) + "<event eventtype=\"3\" eventresult=\"0\" errorcode=\"62\"></event>")) << kRequestXml; } } // namespace chromeos_update_engine diff --git a/omaha_request_params.cc b/omaha_request_params.cc index d4b8d649..8a2e3dcb 100644 --- a/omaha_request_params.cc +++ b/omaha_request_params.cc @@ -66,7 +66,7 @@ bool OmahaRequestParams::Init(const string& in_app_version, image_props_ = LoadImageProperties(system_state_); mutable_image_props_ = LoadMutableImageProperties(system_state_); - // Sanity check the channel names. + // Validation check the channel names. if (!IsValidChannel(image_props_.current_channel)) image_props_.current_channel = "stable-channel"; if (!IsValidChannel(mutable_image_props_.target_channel)) diff --git a/otacerts.zip b/otacerts.zip Binary files differnew file mode 100644 index 00000000..00a5a513 --- /dev/null +++ b/otacerts.zip diff --git a/payload_consumer/certificate_parser_android.cc b/payload_consumer/certificate_parser_android.cc new file mode 100644 index 00000000..4a20547a --- /dev/null +++ b/payload_consumer/certificate_parser_android.cc @@ -0,0 +1,121 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_consumer/certificate_parser_android.h" + +#include <memory> +#include <utility> + +#include <base/logging.h> +#include <openssl/bio.h> +#include <openssl/pem.h> +#include <ziparchive/zip_archive.h> + +#include "update_engine/payload_consumer/certificate_parser_interface.h" + +namespace { +bool IterateZipEntriesAndSearchForKeys( + const ZipArchiveHandle& handle, std::vector<std::vector<uint8_t>>* result) { + void* cookie; + int32_t iter_status = StartIteration(handle, &cookie, "", "x509.pem"); + if (iter_status != 0) { + LOG(ERROR) << "Failed to iterate over entries in the certificate zipfile: " + << ErrorCodeString(iter_status); + return false; + } + std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration); + + std::vector<std::vector<uint8_t>> pem_keys; + std::string_view name; + ZipEntry entry; + while ((iter_status = Next(cookie, &entry, &name)) == 0) { + std::vector<uint8_t> pem_content(entry.uncompressed_length); + if (int32_t extract_status = ExtractToMemory( + handle, &entry, pem_content.data(), pem_content.size()); + extract_status != 0) { + LOG(ERROR) << "Failed to extract " << name << ": " + << ErrorCodeString(extract_status); + return false; + } + pem_keys.push_back(pem_content); + } + + if (iter_status != -1) { + LOG(ERROR) << "Error while iterating over zip entries: " + << ErrorCodeString(iter_status); + return false; + } + + *result = std::move(pem_keys); + return true; +} + +} // namespace + +namespace chromeos_update_engine { +bool CertificateParserAndroid::ReadPublicKeysFromCertificates( + const std::string& path, + std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>* + out_public_keys) { + out_public_keys->clear(); + + ZipArchiveHandle handle; + if (int32_t open_status = OpenArchive(path.c_str(), &handle); + open_status != 0) { + LOG(ERROR) << "Failed to open " << path << ": " + << ErrorCodeString(open_status); + return false; + } + + std::vector<std::vector<uint8_t>> pem_certs; + if (!IterateZipEntriesAndSearchForKeys(handle, &pem_certs)) { + CloseArchive(handle); + return false; + } + CloseArchive(handle); + + // Convert the certificates into public keys. Stop and return false if we + // encounter an error. + std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>> result; + for (const auto& cert : pem_certs) { + std::unique_ptr<BIO, decltype(&BIO_free)> input( + BIO_new_mem_buf(cert.data(), cert.size()), BIO_free); + + std::unique_ptr<X509, decltype(&X509_free)> x509( + PEM_read_bio_X509(input.get(), nullptr, nullptr, nullptr), X509_free); + if (!x509) { + LOG(ERROR) << "Failed to read x509 certificate"; + return false; + } + + std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> public_key( + X509_get_pubkey(x509.get()), EVP_PKEY_free); + if (!public_key) { + LOG(ERROR) << "Failed to extract the public key from x509 certificate"; + return false; + } + result.push_back(std::move(public_key)); + } + + *out_public_keys = std::move(result); + return true; +} + +std::unique_ptr<CertificateParserInterface> CreateCertificateParser() { + return std::make_unique<CertificateParserAndroid>(); +} + +} // namespace chromeos_update_engine diff --git a/payload_consumer/certificate_parser_android.h b/payload_consumer/certificate_parser_android.h new file mode 100644 index 00000000..ccb92936 --- /dev/null +++ b/payload_consumer/certificate_parser_android.h @@ -0,0 +1,46 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_CERTIFICATE_PARSER_ANDROID_H_ +#define UPDATE_ENGINE_CERTIFICATE_PARSER_ANDROID_H_ + +#include <memory> +#include <string> +#include <vector> + +#include <base/macros.h> + +#include "payload_consumer/certificate_parser_interface.h" + +namespace chromeos_update_engine { +// This class parses the certificates from a zip file. Because the Android +// build system stores the certs in otacerts.zip. +class CertificateParserAndroid : public CertificateParserInterface { + public: + CertificateParserAndroid() = default; + + bool ReadPublicKeysFromCertificates( + const std::string& path, + std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>* + out_public_keys) override; + + private: + DISALLOW_COPY_AND_ASSIGN(CertificateParserAndroid); +}; + +} // namespace chromeos_update_engine + +#endif diff --git a/payload_consumer/certificate_parser_android_unittest.cc b/payload_consumer/certificate_parser_android_unittest.cc new file mode 100644 index 00000000..e300414b --- /dev/null +++ b/payload_consumer/certificate_parser_android_unittest.cc @@ -0,0 +1,61 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_consumer/certificate_parser_interface.h" + +#include <string> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "update_engine/common/hash_calculator.h" +#include "update_engine/common/test_utils.h" +#include "update_engine/common/utils.h" +#include "update_engine/payload_consumer/payload_verifier.h" +#include "update_engine/payload_generator/payload_signer.h" + +namespace chromeos_update_engine { + +extern const char* kUnittestPrivateKeyPath; +const char* kUnittestOtacertsPath = "otacerts.zip"; + +TEST(CertificateParserAndroidTest, ParseZipArchive) { + std::string ota_cert = + test_utils::GetBuildArtifactsPath(kUnittestOtacertsPath); + ASSERT_TRUE(utils::FileExists(ota_cert.c_str())); + + std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>> keys; + auto parser = CreateCertificateParser(); + ASSERT_TRUE(parser->ReadPublicKeysFromCertificates(ota_cert, &keys)); + ASSERT_EQ(1u, keys.size()); +} + +TEST(CertificateParserAndroidTest, VerifySignature) { + brillo::Blob hash_blob; + ASSERT_TRUE(HashCalculator::RawHashOfData({'x'}, &hash_blob)); + brillo::Blob sig_blob; + ASSERT_TRUE(PayloadSigner::SignHash( + hash_blob, + test_utils::GetBuildArtifactsPath(kUnittestPrivateKeyPath), + &sig_blob)); + + auto verifier = PayloadVerifier::CreateInstanceFromZipPath( + test_utils::GetBuildArtifactsPath(kUnittestOtacertsPath)); + ASSERT_TRUE(verifier != nullptr); + ASSERT_TRUE(verifier->VerifyRawSignature(sig_blob, hash_blob, nullptr)); +} + +} // namespace chromeos_update_engine diff --git a/payload_consumer/certificate_parser_interface.h b/payload_consumer/certificate_parser_interface.h new file mode 100644 index 00000000..dad23d21 --- /dev/null +++ b/payload_consumer/certificate_parser_interface.h @@ -0,0 +1,44 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_CERTIFICATE_PARSER_INTERFACE_H_ +#define UPDATE_ENGINE_CERTIFICATE_PARSER_INTERFACE_H_ + +#include <memory> +#include <string> +#include <vector> + +#include <openssl/pem.h> + +namespace chromeos_update_engine { + +// This class parses the PEM encoded X509 certificates from |path|; and +// passes the parsed public keys to the caller. +class CertificateParserInterface { + public: + virtual ~CertificateParserInterface() = default; + + virtual bool ReadPublicKeysFromCertificates( + const std::string& path, + std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>* + out_public_keys) = 0; +}; + +std::unique_ptr<CertificateParserInterface> CreateCertificateParser(); + +} // namespace chromeos_update_engine + +#endif diff --git a/payload_consumer/certificate_parser_stub.cc b/payload_consumer/certificate_parser_stub.cc new file mode 100644 index 00000000..a365ab84 --- /dev/null +++ b/payload_consumer/certificate_parser_stub.cc @@ -0,0 +1,31 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_consumer/certificate_parser_stub.h" + +namespace chromeos_update_engine { +bool CertificateParserStub::ReadPublicKeysFromCertificates( + const std::string& path, + std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>* + out_public_keys) { + return true; +} + +std::unique_ptr<CertificateParserInterface> CreateCertificateParser() { + return std::make_unique<CertificateParserStub>(); +} + +} // namespace chromeos_update_engine diff --git a/payload_consumer/certificate_parser_stub.h b/payload_consumer/certificate_parser_stub.h new file mode 100644 index 00000000..a51c2c67 --- /dev/null +++ b/payload_consumer/certificate_parser_stub.h @@ -0,0 +1,44 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_CERTIFICATE_PARSER_STUB_H_ +#define UPDATE_ENGINE_CERTIFICATE_PARSER_STUB_H_ + +#include <memory> +#include <string> +#include <vector> + +#include <base/macros.h> + +#include "update_engine/payload_consumer/certificate_parser_interface.h" + +namespace chromeos_update_engine { +class CertificateParserStub : public CertificateParserInterface { + public: + CertificateParserStub() = default; + + bool ReadPublicKeysFromCertificates( + const std::string& path, + std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>* + out_public_keys) override; + + private: + DISALLOW_COPY_AND_ASSIGN(CertificateParserStub); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_CERTIFICATE_PARSER_STUB_H_ diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc index 95dfbcc7..7375d37f 100644 --- a/payload_consumer/delta_performer.cc +++ b/payload_consumer/delta_performer.cc @@ -23,6 +23,7 @@ #include <cstring> #include <map> #include <memory> +#include <set> #include <string> #include <utility> #include <vector> @@ -40,15 +41,19 @@ #include <puffin/puffpatch.h> #include "update_engine/common/constants.h" +#include "update_engine/common/error_code.h" +#include "update_engine/common/error_code_utils.h" #include "update_engine/common/hardware_interface.h" #include "update_engine/common/prefs_interface.h" #include "update_engine/common/subprocess.h" #include "update_engine/common/terminator.h" #include "update_engine/payload_consumer/bzip_extent_writer.h" #include "update_engine/payload_consumer/cached_file_descriptor.h" +#include "update_engine/payload_consumer/certificate_parser_interface.h" #include "update_engine/payload_consumer/download_action.h" #include "update_engine/payload_consumer/extent_reader.h" #include "update_engine/payload_consumer/extent_writer.h" +#include "update_engine/payload_consumer/partition_update_generator_interface.h" #if USE_FEC #include "update_engine/payload_consumer/fec_file_descriptor.h" #endif // USE_FEC @@ -315,8 +320,14 @@ bool DeltaPerformer::OpenCurrentPartition() { install_plan_->partitions.size() - partitions_.size(); const InstallPlan::Partition& install_part = install_plan_->partitions[num_previous_partitions + current_partition_]; - // Open source fds if we have a delta payload. - if (payload_->type == InstallPayloadType::kDelta) { + // Open source fds if we have a delta payload, or for partitions in the + // partial update. + bool source_may_exist = manifest_.partial_update() || + payload_->type == InstallPayloadType::kDelta; + // We shouldn't open the source partition in certain cases, e.g. some dynamic + // partitions in delta payload, partitions included in the full payload for + // partial updates. Use the source size as the indicator. + if (source_may_exist && install_part.source_size > 0) { source_path_ = install_part.source_path; int err; source_fd_ = OpenFile(source_path_.c_str(), O_RDONLY, false, &err); @@ -495,17 +506,19 @@ MetadataParseResult DeltaPerformer::ParsePayloadMetadata( << "Trusting metadata size in payload = " << metadata_size_; } - string public_key; - if (!GetPublicKey(&public_key)) { - LOG(ERROR) << "Failed to get public key."; + auto [payload_verifier, perform_verification] = CreatePayloadVerifier(); + if (!payload_verifier) { + LOG(ERROR) << "Failed to create payload verifier."; *error = ErrorCode::kDownloadMetadataSignatureVerificationError; - return MetadataParseResult::kError; + if (perform_verification) { + return MetadataParseResult::kError; + } + } else { + // We have the full metadata in |payload|. Verify its integrity + // and authenticity based on the information we have in Omaha response. + *error = payload_metadata_.ValidateMetadataSignature( + payload, payload_->metadata_signature, *payload_verifier); } - - // We have the full metadata in |payload|. Verify its integrity - // and authenticity based on the information we have in Omaha response. - *error = payload_metadata_.ValidateMetadataSignature( - payload, payload_->metadata_signature, public_key); if (*error != ErrorCode::kSuccess) { if (install_plan_->hash_checks_mandatory) { // The autoupdate_CatchBadSignatures test checks for this string @@ -574,6 +587,9 @@ bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode* error) { if ((*error = ValidateManifest()) != ErrorCode::kSuccess) return false; manifest_valid_ = true; + if (!install_plan_->is_resume) { + prefs_->SetString(kPrefsManifestBytes, {buffer_.begin(), buffer_.end()}); + } // Clear the download buffer. DiscardBuffer(false, metadata_size_); @@ -726,7 +742,7 @@ bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode* error) { CheckpointUpdateProgress(false); } - // In major version 2, we don't add dummy operation to the payload. + // In major version 2, we don't add unused operation to the payload. // If we already extracted the signature we should skip this step. if (manifest_.has_signatures_offset() && manifest_.has_signatures_size() && signatures_message_data_.empty()) { @@ -767,7 +783,61 @@ bool DeltaPerformer::ParseManifestPartitions(ErrorCode* error) { for (const PartitionUpdate& partition : manifest_.partitions()) { partitions_.push_back(partition); } + + // For VAB and partial updates, the partition preparation will copy the + // dynamic partitions metadata to the target metadata slot, and rename the + // slot suffix of the partitions in the metadata. + if (install_plan_->target_slot != BootControlInterface::kInvalidSlot) { + uint64_t required_size = 0; + if (!PreparePartitionsForUpdate(&required_size)) { + if (required_size > 0) { + *error = ErrorCode::kNotEnoughSpace; + } else { + *error = ErrorCode::kInstallDeviceOpenError; + } + return false; + } + } + + // Partitions in manifest are no longer needed after preparing partitions. manifest_.clear_partitions(); + // TODO(xunchang) TBD: allow partial update only on devices with dynamic + // partition. + if (manifest_.partial_update()) { + std::set<std::string> touched_partitions; + for (const auto& partition_update : partitions_) { + touched_partitions.insert(partition_update.partition_name()); + } + + auto generator = partition_update_generator::Create(boot_control_, + manifest_.block_size()); + std::vector<PartitionUpdate> untouched_static_partitions; + TEST_AND_RETURN_FALSE( + generator->GenerateOperationsForPartitionsNotInPayload( + install_plan_->source_slot, + install_plan_->target_slot, + touched_partitions, + &untouched_static_partitions)); + partitions_.insert(partitions_.end(), + untouched_static_partitions.begin(), + untouched_static_partitions.end()); + + // Save the untouched dynamic partitions in install plan. + std::vector<std::string> dynamic_partitions; + if (!boot_control_->GetDynamicPartitionControl() + ->ListDynamicPartitionsForSlot(install_plan_->source_slot, + &dynamic_partitions)) { + LOG(ERROR) << "Failed to load dynamic partitions from slot " + << install_plan_->source_slot; + return false; + } + install_plan_->untouched_dynamic_partitions.clear(); + for (const auto& name : dynamic_partitions) { + if (touched_partitions.find(name) == touched_partitions.end()) { + install_plan_->untouched_dynamic_partitions.push_back(name); + } + } + } // Fill in the InstallPlan::partitions based on the partitions from the // payload. @@ -842,13 +912,9 @@ bool DeltaPerformer::ParseManifestPartitions(ErrorCode* error) { install_plan_->partitions.push_back(install_part); } - if (install_plan_->target_slot != BootControlInterface::kInvalidSlot) { - if (!InitPartitionMetadata()) { - *error = ErrorCode::kInstallDeviceOpenError; - return false; - } - } - + // TODO(xunchang) only need to load the partitions for those in payload. + // Because we have already loaded the other once when generating SOURCE_COPY + // operations. if (!install_plan_->LoadPartitionsFromSlots(boot_control_)) { LOG(ERROR) << "Unable to determine all the partition devices."; *error = ErrorCode::kInstallDeviceOpenError; @@ -858,45 +924,57 @@ bool DeltaPerformer::ParseManifestPartitions(ErrorCode* error) { return true; } -bool DeltaPerformer::InitPartitionMetadata() { - BootControlInterface::PartitionMetadata partition_metadata; - if (manifest_.has_dynamic_partition_metadata()) { - std::map<string, uint64_t> partition_sizes; - for (const auto& partition : install_plan_->partitions) { - partition_sizes.emplace(partition.name, partition.target_size); - } - for (const auto& group : manifest_.dynamic_partition_metadata().groups()) { - BootControlInterface::PartitionMetadata::Group e; - e.name = group.name(); - e.size = group.size(); - for (const auto& partition_name : group.partition_names()) { - auto it = partition_sizes.find(partition_name); - if (it == partition_sizes.end()) { - // TODO(tbao): Support auto-filling partition info for framework-only - // OTA. - LOG(ERROR) << "dynamic_partition_metadata contains partition " - << partition_name - << " but it is not part of the manifest. " - << "This is not supported."; - return false; - } - e.partitions.push_back({partition_name, it->second}); - } - partition_metadata.groups.push_back(std::move(e)); - } +bool DeltaPerformer::PreparePartitionsForUpdate(uint64_t* required_size) { + // Call static PreparePartitionsForUpdate with hash from + // kPrefsUpdateCheckResponseHash to ensure hash of payload that space is + // preallocated for is the same as the hash of payload being applied. + string update_check_response_hash; + ignore_result(prefs_->GetString(kPrefsUpdateCheckResponseHash, + &update_check_response_hash)); + return PreparePartitionsForUpdate(prefs_, + boot_control_, + install_plan_->target_slot, + manifest_, + update_check_response_hash, + required_size); +} + +bool DeltaPerformer::PreparePartitionsForUpdate( + PrefsInterface* prefs, + BootControlInterface* boot_control, + BootControlInterface::Slot target_slot, + const DeltaArchiveManifest& manifest, + const std::string& update_check_response_hash, + uint64_t* required_size) { + string last_hash; + ignore_result( + prefs->GetString(kPrefsDynamicPartitionMetadataUpdated, &last_hash)); + + bool is_resume = !update_check_response_hash.empty() && + last_hash == update_check_response_hash; + + if (is_resume) { + LOG(INFO) << "Using previously prepared partitions for update. hash = " + << last_hash; + } else { + LOG(INFO) << "Preparing partitions for new update. last hash = " + << last_hash << ", new hash = " << update_check_response_hash; } - bool metadata_updated = false; - prefs_->GetBoolean(kPrefsDynamicPartitionMetadataUpdated, &metadata_updated); - if (!boot_control_->InitPartitionMetadata( - install_plan_->target_slot, partition_metadata, !metadata_updated)) { + if (!boot_control->GetDynamicPartitionControl()->PreparePartitionsForUpdate( + boot_control->GetCurrentSlot(), + target_slot, + manifest, + !is_resume /* should update */, + required_size)) { LOG(ERROR) << "Unable to initialize partition metadata for slot " - << BootControlInterface::SlotName(install_plan_->target_slot); + << BootControlInterface::SlotName(target_slot); return false; } - TEST_AND_RETURN_FALSE( - prefs_->SetBoolean(kPrefsDynamicPartitionMetadataUpdated, true)); - LOG(INFO) << "InitPartitionMetadata done."; + + TEST_AND_RETURN_FALSE(prefs->SetString(kPrefsDynamicPartitionMetadataUpdated, + update_check_response_hash)); + LOG(INFO) << "PreparePartitionsForUpdate done."; return true; } @@ -1031,7 +1109,21 @@ bool DeltaPerformer::PerformSourceCopyOperation( if (operation.has_dst_length()) TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0); + TEST_AND_RETURN_FALSE(source_fd_ != nullptr); + + // The device may optimize the SOURCE_COPY operation. + // Being this a device-specific optimization let DynamicPartitionController + // decide it the operation should be skipped. + const PartitionUpdate& partition = partitions_[current_partition_]; + const auto& partition_control = boot_control_->GetDynamicPartitionControl(); + + InstallOperation buf; + bool should_optimize = partition_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()); @@ -1040,15 +1132,30 @@ bool DeltaPerformer::PerformSourceCopyOperation( // 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. - bool read_ok = fd_utils::CopyAndHashExtents(source_fd_, - operation.src_extents(), - target_fd_, - operation.dst_extents(), - block_size_, - &source_hash); + 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 @@ -1061,13 +1168,25 @@ bool DeltaPerformer::PerformSourceCopyOperation( << ", expected " << base::HexEncode(expected_source_hash.data(), expected_source_hash.size()); - - TEST_AND_RETURN_FALSE(fd_utils::CopyAndHashExtents(source_ecc_fd_, - operation.src_extents(), - target_fd_, - operation.dst_extents(), - block_size_, - &source_hash)); + 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 the error corrected device worked, but @@ -1079,19 +1198,20 @@ bool DeltaPerformer::PerformSourceCopyOperation( // 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_, - operation.src_extents(), + optimized.src_extents(), target_fd_, - operation.dst_extents(), + optimized.dst_extents(), block_size_, nullptr)) { return true; } TEST_AND_RETURN_FALSE(fd_utils::CopyAndHashExtents(source_fd_, - operation.src_extents(), + optimized.src_extents(), target_fd_, - operation.dst_extents(), + optimized.dst_extents(), block_size_, nullptr)); } @@ -1100,6 +1220,11 @@ bool DeltaPerformer::PerformSourceCopyOperation( FileDescriptorPtr DeltaPerformer::ChooseSourceFD( const InstallOperation& operation, ErrorCode* error) { + if (source_fd_ == nullptr) { + LOG(ERROR) << "ChooseSourceFD fail: source_fd_ == nullptr"; + return nullptr; + } + if (!operation.has_src_sha256_hash()) { // 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 @@ -1396,8 +1521,7 @@ bool DeltaPerformer::ExtractSignatureMessage() { // blob and the signed sha-256 context. LOG_IF(WARNING, !prefs_->SetString(kPrefsUpdateStateSignatureBlob, - string(signatures_message_data_.begin(), - signatures_message_data_.end()))) + signatures_message_data_)) << "Unable to store the signature blob."; LOG(INFO) << "Extracted signature data of size " @@ -1421,14 +1545,35 @@ bool DeltaPerformer::GetPublicKey(string* out_public_key) { return brillo::data_encoding::Base64Decode(install_plan_->public_key_rsa, out_public_key); } - + LOG(INFO) << "No public keys found for verification."; return true; } +std::pair<std::unique_ptr<PayloadVerifier>, bool> +DeltaPerformer::CreatePayloadVerifier() { + if (utils::FileExists(update_certificates_path_.c_str())) { + LOG(INFO) << "Verifying using certificates: " << update_certificates_path_; + return { + PayloadVerifier::CreateInstanceFromZipPath(update_certificates_path_), + true}; + } + + string public_key; + if (!GetPublicKey(&public_key)) { + LOG(ERROR) << "Failed to read public key"; + return {nullptr, true}; + } + + // Skips the verification if the public key is empty. + if (public_key.empty()) { + return {nullptr, false}; + } + return {PayloadVerifier::CreateInstance(public_key), true}; +} + ErrorCode DeltaPerformer::ValidateManifest() { - // Perform assorted checks to sanity check the manifest, make sure it + // Perform assorted checks to validation check the manifest, make sure it // matches data from other sources, and that it is a supported version. - bool has_old_fields = std::any_of(manifest_.partitions().begin(), manifest_.partitions().end(), [](const PartitionUpdate& partition) { @@ -1436,9 +1581,12 @@ ErrorCode DeltaPerformer::ValidateManifest() { }); // The presence of an old partition hash is the sole indicator for a delta - // update. + // update. Also, always treat the partial update as delta so that we can + // perform the minor version check correctly. InstallPayloadType actual_payload_type = - has_old_fields ? InstallPayloadType::kDelta : InstallPayloadType::kFull; + (has_old_fields || manifest_.partial_update()) + ? InstallPayloadType::kDelta + : InstallPayloadType::kFull; if (payload_->type == InstallPayloadType::kUnknown) { LOG(INFO) << "Detected a '" @@ -1453,8 +1601,8 @@ ErrorCode DeltaPerformer::ValidateManifest() { << "' payload."; return ErrorCode::kPayloadMismatchedType; } - // Check that the minor version is compatible. + // TODO(xunchang) increment minor version & add check for partial update if (actual_payload_type == InstallPayloadType::kFull) { if (manifest_.minor_version() != kFullPayloadMinorVersion) { LOG(ERROR) << "Manifest contains minor version " @@ -1475,6 +1623,88 @@ ErrorCode DeltaPerformer::ValidateManifest() { } } + ErrorCode error_code = CheckTimestampError(); + if (error_code != ErrorCode::kSuccess) { + if (error_code == ErrorCode::kPayloadTimestampError) { + if (!hardware_->AllowDowngrade()) { + return ErrorCode::kPayloadTimestampError; + } + LOG(INFO) << "The current OS build allows downgrade, continuing to apply" + " the payload with an older timestamp."; + } else { + LOG(ERROR) << "Timestamp check returned " + << utils::ErrorCodeToString(error_code); + return error_code; + } + } + + // TODO(crbug.com/37661) we should be adding more and more manifest checks, + // such as partition boundaries, etc. + + return ErrorCode::kSuccess; +} + +ErrorCode DeltaPerformer::CheckTimestampError() const { + bool is_partial_update = + manifest_.has_partial_update() && manifest_.partial_update(); + const auto& partitions = manifest_.partitions(); + + // Check version field for a given PartitionUpdate object. If an error + // is encountered, set |error_code| accordingly. If downgrade is detected, + // |downgrade_detected| is set. Return true if the program should continue to + // check the next partition or not, or false if it should exit early due to + // errors. + auto&& timestamp_valid = [this](const PartitionUpdate& partition, + bool allow_empty_version, + bool* downgrade_detected) -> ErrorCode { + if (!partition.has_version()) { + if (allow_empty_version) { + return ErrorCode::kSuccess; + } + LOG(ERROR) + << "PartitionUpdate " << partition.partition_name() + << " does ot have a version field. Not allowed in partial updates."; + return ErrorCode::kDownloadManifestParseError; + } + + auto error_code = hardware_->IsPartitionUpdateValid( + partition.partition_name(), partition.version()); + switch (error_code) { + case ErrorCode::kSuccess: + break; + case ErrorCode::kPayloadTimestampError: + *downgrade_detected = true; + LOG(WARNING) << "PartitionUpdate " << partition.partition_name() + << " has an older version than partition on device."; + break; + default: + LOG(ERROR) << "IsPartitionUpdateValid(" << partition.partition_name() + << ") returned" << utils::ErrorCodeToString(error_code); + break; + } + return error_code; + }; + + bool downgrade_detected = false; + + if (is_partial_update) { + // for partial updates, all partition MUST have valid timestamps + // But max_timestamp can be empty + for (const auto& partition : partitions) { + auto error_code = timestamp_valid( + partition, false /* allow_empty_version */, &downgrade_detected); + if (error_code != ErrorCode::kSuccess && + error_code != ErrorCode::kPayloadTimestampError) { + return error_code; + } + } + if (downgrade_detected) { + return ErrorCode::kPayloadTimestampError; + } + return ErrorCode::kSuccess; + } + + // For non-partial updates, check max_timestamp first. if (manifest_.max_timestamp() < hardware_->GetBuildTimestamp()) { LOG(ERROR) << "The current OS build timestamp (" << hardware_->GetBuildTimestamp() @@ -1482,10 +1712,18 @@ ErrorCode DeltaPerformer::ValidateManifest() { << manifest_.max_timestamp() << ")"; return ErrorCode::kPayloadTimestampError; } - - // TODO(crbug.com/37661) we should be adding more and more manifest checks, - // such as partition boundaries, etc. - + // Otherwise... partitions can have empty timestamps. + for (const auto& partition : partitions) { + auto error_code = timestamp_valid( + partition, true /* allow_empty_version */, &downgrade_detected); + if (error_code != ErrorCode::kSuccess && + error_code != ErrorCode::kPayloadTimestampError) { + return error_code; + } + } + if (downgrade_detected) { + return ErrorCode::kPayloadTimestampError; + } return ErrorCode::kSuccess; } @@ -1505,7 +1743,7 @@ ErrorCode DeltaPerformer::ValidateOperationHash( // corresponding update should have been produced with the operation // hashes. So if it happens it means either we've turned operation hash // generation off in DeltaDiffGenerator or it's a regression of some sort. - // One caveat though: The last operation is a dummy signature operation + // One caveat though: The last operation is a unused signature operation // that doesn't have a hash at the time the manifest is created. So we // should not complaint about that operation. This operation can be // recognized by the fact that it's offset is mentioned in the manifest. @@ -1563,12 +1801,6 @@ ErrorCode DeltaPerformer::ValidateOperationHash( ErrorCode DeltaPerformer::VerifyPayload( const brillo::Blob& update_check_response_hash, const uint64_t update_check_response_size) { - string public_key; - if (!GetPublicKey(&public_key)) { - LOG(ERROR) << "Failed to get public key."; - return ErrorCode::kDownloadPayloadPubKeyVerificationError; - } - // Verifies the download size. if (update_check_response_size != metadata_size_ + metadata_signature_size_ + buffer_offset_) { @@ -1579,6 +1811,16 @@ ErrorCode DeltaPerformer::VerifyPayload( return ErrorCode::kPayloadSizeMismatchError; } + auto [payload_verifier, perform_verification] = CreatePayloadVerifier(); + if (!perform_verification) { + LOG(WARNING) << "Not verifying signed delta payload -- missing public key."; + return ErrorCode::kSuccess; + } + if (!payload_verifier) { + LOG(ERROR) << "Failed to create the payload verifier."; + return ErrorCode::kDownloadPayloadPubKeyVerificationError; + } + // Verifies the payload hash. TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadVerificationError, !payload_hash_calculator_.raw_hash().empty()); @@ -1586,21 +1828,13 @@ ErrorCode DeltaPerformer::VerifyPayload( ErrorCode::kPayloadHashMismatchError, payload_hash_calculator_.raw_hash() == update_check_response_hash); - // Verifies the signed payload hash. - if (public_key.empty()) { - LOG(WARNING) << "Not verifying signed delta payload -- missing public key."; - return ErrorCode::kSuccess; - } TEST_AND_RETURN_VAL(ErrorCode::kSignedDeltaPayloadExpectedError, !signatures_message_data_.empty()); brillo::Blob hash_data = signed_hash_calculator_.raw_hash(); TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError, - PayloadVerifier::PadRSA2048SHA256Hash(&hash_data)); - TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError, - !hash_data.empty()); + hash_data.size() == kSHA256Size); - if (!PayloadVerifier::VerifySignature( - signatures_message_data_, public_key, hash_data)) { + if (!payload_verifier->VerifySignature(signatures_message_data_, hash_data)) { // The autoupdate_CatchBadSignatures test checks for this string // in log-files. Keep in sync. LOG(ERROR) << "Public key verification failed, thus update failed."; @@ -1645,7 +1879,7 @@ bool DeltaPerformer::CanResumeUpdate(PrefsInterface* prefs, resumed_update_failures > kMaxResumedUpdateFailures) return false; - // Sanity check the rest. + // Validation check the rest. int64_t next_data_offset = -1; if (!(prefs->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset) && next_data_offset >= 0)) @@ -1670,7 +1904,10 @@ bool DeltaPerformer::CanResumeUpdate(PrefsInterface* prefs, return true; } -bool DeltaPerformer::ResetUpdateProgress(PrefsInterface* prefs, bool quick) { +bool DeltaPerformer::ResetUpdateProgress( + PrefsInterface* prefs, + bool quick, + bool skip_dynamic_partititon_metadata_updated) { TEST_AND_RETURN_FALSE(prefs->SetInt64(kPrefsUpdateStateNextOperation, kUpdateStateOperationInvalid)); if (!quick) { @@ -1684,7 +1921,11 @@ bool DeltaPerformer::ResetUpdateProgress(PrefsInterface* prefs, bool quick) { prefs->SetInt64(kPrefsResumedUpdateFailures, 0); prefs->Delete(kPrefsPostInstallSucceeded); prefs->Delete(kPrefsVerityWritten); - prefs->Delete(kPrefsDynamicPartitionMetadataUpdated); + + if (!skip_dynamic_partititon_metadata_updated) { + LOG(INFO) << "Resetting recorded hash for prepared partitions."; + prefs->Delete(kPrefsDynamicPartitionMetadataUpdated); + } } return true; } @@ -1758,11 +1999,7 @@ bool DeltaPerformer::PrimeUpdateState() { signed_hash_calculator_.SetContext(signed_hash_context)); } - string signature_blob; - if (prefs_->GetString(kPrefsUpdateStateSignatureBlob, &signature_blob)) { - signatures_message_data_.assign(signature_blob.begin(), - signature_blob.end()); - } + prefs_->GetString(kPrefsUpdateStateSignatureBlob, &signatures_message_data_); string hash_context; TEST_AND_RETURN_FALSE( diff --git a/payload_consumer/delta_performer.h b/payload_consumer/delta_performer.h index 78607475..88076af3 100644 --- a/payload_consumer/delta_performer.h +++ b/payload_consumer/delta_performer.h @@ -20,7 +20,9 @@ #include <inttypes.h> #include <limits> +#include <memory> #include <string> +#include <utility> #include <vector> #include <base/time/time.h> @@ -34,6 +36,7 @@ #include "update_engine/payload_consumer/file_writer.h" #include "update_engine/payload_consumer/install_plan.h" #include "update_engine/payload_consumer/payload_metadata.h" +#include "update_engine/payload_consumer/payload_verifier.h" #include "update_engine/update_metadata.pb.h" namespace chromeos_update_engine { @@ -45,7 +48,6 @@ class PrefsInterface; // This class performs the actions in a delta update synchronously. The delta // update itself should be passed in in chunks as it is received. - class DeltaPerformer : public FileWriter { public: // Defines the granularity of progress logging in terms of how many "completed @@ -75,7 +77,9 @@ class DeltaPerformer : public FileWriter { download_delegate_(download_delegate), install_plan_(install_plan), payload_(payload), - interactive_(interactive) {} + interactive_(interactive) { + CHECK(install_plan_); + } // FileWriter's Write implementation where caller doesn't care about // error codes. @@ -140,9 +144,14 @@ class DeltaPerformer : public FileWriter { // Resets the persistent update progress state to indicate that an update // can't be resumed. Performs a quick update-in-progress reset if |quick| is - // true, otherwise resets all progress-related update state. Returns true on - // success, false otherwise. - static bool ResetUpdateProgress(PrefsInterface* prefs, bool quick); + // true, otherwise resets all progress-related update state. + // If |skip_dynamic_partititon_metadata_updated| is true, do not reset + // dynamic-partition-metadata-updated. + // Returns true on success, false otherwise. + static bool ResetUpdateProgress( + PrefsInterface* prefs, + bool quick, + bool skip_dynamic_partititon_metadata_updated = false); // Attempts to parse the update metadata starting from the beginning of // |payload|. On success, returns kMetadataParseSuccess. Returns @@ -156,6 +165,11 @@ class DeltaPerformer : public FileWriter { public_key_path_ = public_key_path; } + void set_update_certificates_path( + const std::string& update_certificates_path) { + update_certificates_path_ = update_certificates_path; + } + // Return true if header parsing is finished and no errors occurred. bool IsHeaderParsed() const; @@ -171,6 +185,24 @@ class DeltaPerformer : public FileWriter { const FileDescriptorPtr source_fd, ErrorCode* error); + // Initialize partitions and allocate required space for an update with the + // given |manifest|. |update_check_response_hash| is used to check if the + // previous call to this function corresponds to the same payload. + // - Same payload: not make any persistent modifications (not write to disk) + // - Different payload: make persistent modifications (write to disk) + // In both cases, in-memory flags are updated. This function must be called + // on the payload at least once (to update in-memory flags) before writing + // (applying) the payload. + // If error due to insufficient space, |required_size| is set to the required + // size on the device to apply the payload. + static bool PreparePartitionsForUpdate( + PrefsInterface* prefs, + BootControlInterface* boot_control, + BootControlInterface::Slot target_slot, + const DeltaArchiveManifest& manifest, + const std::string& update_check_response_hash, + uint64_t* required_size); + private: friend class DeltaPerformerTest; friend class DeltaPerformerIntegrationTest; @@ -266,9 +298,25 @@ class DeltaPerformer : public FileWriter { // |out_public_key|. Returns false on failures. bool GetPublicKey(std::string* out_public_key); + // Creates a PayloadVerifier from the zip file containing certificates. If the + // path to the zip file doesn't exist, falls back to use the public key. + // Returns a tuple with the created PayloadVerifier and if we should perform + // the verification. + std::pair<std::unique_ptr<PayloadVerifier>, bool> CreatePayloadVerifier(); + // After install_plan_ is filled with partition names and sizes, initialize // metadata of partitions and map necessary devices before opening devices. - bool InitPartitionMetadata(); + // Also see comment for the static PreparePartitionsForUpdate(). + bool PreparePartitionsForUpdate(uint64_t* required_size); + + // Check if current manifest contains timestamp errors. + // Return: + // - kSuccess if update is valid. + // - kPayloadTimestampError if downgrade is detected + // - kDownloadManifestParseError if |new_version| has an incorrect format + // - Other error values if the source of error is known, or kError for + // a generic error on the device. + ErrorCode CheckTimestampError() const; // Update Engine preference store. PrefsInterface* prefs_; @@ -370,12 +418,15 @@ class DeltaPerformer : public FileWriter { HashCalculator signed_hash_calculator_; // Signatures message blob extracted directly from the payload. - brillo::Blob signatures_message_data_; + std::string signatures_message_data_; // The public key to be used. Provided as a member so that tests can // override with test keys. std::string public_key_path_{constants::kUpdatePayloadPublicKeyPath}; + // The path to the zip file with X509 certificates. + std::string update_certificates_path_{constants::kUpdateCertificatesPath}; + // The number of bytes received so far, used for progress tracking. size_t total_bytes_received_{0}; diff --git a/payload_consumer/delta_performer_integration_test.cc b/payload_consumer/delta_performer_integration_test.cc index af6682a4..f2aeb03a 100644 --- a/payload_consumer/delta_performer_integration_test.cc +++ b/payload_consumer/delta_performer_integration_test.cc @@ -28,6 +28,7 @@ #include <base/stl_util.h> #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> +#include <gmock/gmock-matchers.h> #include <google/protobuf/repeated_field.h> #include <gtest/gtest.h> #include <openssl/pem.h> @@ -35,9 +36,12 @@ #include "update_engine/common/constants.h" #include "update_engine/common/fake_boot_control.h" #include "update_engine/common/fake_hardware.h" +#include "update_engine/common/fake_prefs.h" #include "update_engine/common/mock_prefs.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" +#include "update_engine/hardware_android.h" +#include "update_engine/payload_consumer/install_plan.h" #include "update_engine/payload_consumer/mock_download_action.h" #include "update_engine/payload_consumer/payload_constants.h" #include "update_engine/payload_consumer/payload_metadata.h" @@ -61,6 +65,8 @@ extern const char* kUnittestPrivateKeyPath; extern const char* kUnittestPublicKeyPath; extern const char* kUnittestPrivateKey2Path; extern const char* kUnittestPublicKey2Path; +extern const char* kUnittestPrivateKeyECPath; +extern const char* kUnittestPublicKeyECPath; static const uint32_t kDefaultKernelSize = 4096; // Something small for a test // clang-format off @@ -109,6 +115,7 @@ enum SignatureTest { kSignatureGeneratedPlaceholder, // Insert placeholder signatures, then real. kSignatureGeneratedPlaceholderMismatch, // Insert a wrong sized placeholder. kSignatureGeneratedShell, // Sign the generated payload through shell cmds. + kSignatureGeneratedShellECKey, // Sign with a EC key through shell cmds. kSignatureGeneratedShellBadKey, // Sign with a bad key through shell cmds. kSignatureGeneratedShellRotateCl1, // Rotate key, test client v1 kSignatureGeneratedShellRotateCl2, // Rotate key, test client v2 @@ -121,7 +128,41 @@ enum OperationHashTest { } // namespace -class DeltaPerformerIntegrationTest : public ::testing::Test {}; +class DeltaPerformerIntegrationTest : public ::testing::Test { + public: + void RunManifestValidation(const DeltaArchiveManifest& manifest, + uint64_t major_version, + ErrorCode expected) { + FakePrefs prefs; + InstallPlan::Payload payload; + InstallPlan install_plan; + DeltaPerformer performer{&prefs, + nullptr, + &fake_hardware_, + nullptr, + &install_plan, + &payload, + false /* interactive*/}; + // Delta performer will treat manifest as kDelta payload + // if it's a partial update. + payload.type = manifest.partial_update() ? InstallPayloadType::kDelta + : InstallPayloadType::kFull; + + // The Manifest we are validating. + performer.manifest_.CopyFrom(manifest); + performer.major_payload_version_ = major_version; + + EXPECT_EQ(expected, performer.ValidateManifest()); + } + void AddPartition(DeltaArchiveManifest* manifest, + std::string name, + int timestamp) { + auto& partition = *manifest->add_partitions(); + partition.set_version(std::to_string(timestamp)); + partition.set_partition_name(name); + } + FakeHardware fake_hardware_; +}; static void CompareFilesByBlock(const string& a_file, const string& b_file, @@ -166,29 +207,26 @@ static bool WriteByteAtOffset(const string& path, off_t offset) { return true; } -static size_t GetSignatureSize(const string& private_key_path) { - const brillo::Blob data(1, 'x'); - brillo::Blob hash; - EXPECT_TRUE(HashCalculator::RawHashOfData(data, &hash)); - brillo::Blob signature; - EXPECT_TRUE(PayloadSigner::SignHash(hash, private_key_path, &signature)); - return signature.size(); -} - -static bool InsertSignaturePlaceholder(int signature_size, +static bool InsertSignaturePlaceholder(size_t signature_size, const string& payload_path, uint64_t* out_metadata_size) { vector<brillo::Blob> signatures; signatures.push_back(brillo::Blob(signature_size, 0)); - return PayloadSigner::AddSignatureToPayload( - payload_path, signatures, {}, payload_path, out_metadata_size); + return PayloadSigner::AddSignatureToPayload(payload_path, + {signature_size}, + signatures, + {}, + payload_path, + out_metadata_size); } static void SignGeneratedPayload(const string& payload_path, uint64_t* out_metadata_size) { string private_key_path = GetBuildArtifactsPath(kUnittestPrivateKeyPath); - int signature_size = GetSignatureSize(private_key_path); + size_t signature_size; + ASSERT_TRUE(PayloadSigner::GetMaximumSignatureSize(private_key_path, + &signature_size)); brillo::Blob metadata_hash, payload_hash; ASSERT_TRUE(PayloadSigner::HashPayloadForSigning( payload_path, {signature_size}, &payload_hash, &metadata_hash)); @@ -198,6 +236,7 @@ static void SignGeneratedPayload(const string& payload_path, ASSERT_TRUE(PayloadSigner::SignHash( metadata_hash, private_key_path, &metadata_signature)); ASSERT_TRUE(PayloadSigner::AddSignatureToPayload(payload_path, + {signature_size}, {payload_signature}, {metadata_signature}, payload_path, @@ -206,28 +245,112 @@ static void SignGeneratedPayload(const string& payload_path, payload_path, GetBuildArtifactsPath(kUnittestPublicKeyPath))); } -static void SignHashToFile(const string& hash_file, - const string& signature_file, - const string& private_key_file) { - brillo::Blob hash, signature; - ASSERT_TRUE(utils::ReadFile(hash_file, &hash)); - ASSERT_TRUE(PayloadSigner::SignHash(hash, private_key_file, &signature)); - ASSERT_TRUE(test_utils::WriteFileVector(signature_file, signature)); +static void SignGeneratedShellPayloadWithKeys( + const string& payload_path, + const vector<string>& private_key_paths, + const string& public_key_path, + bool verification_success) { + vector<string> signature_size_strings; + for (const auto& key_path : private_key_paths) { + size_t signature_size; + ASSERT_TRUE( + PayloadSigner::GetMaximumSignatureSize(key_path, &signature_size)); + signature_size_strings.push_back(base::StringPrintf("%zu", signature_size)); + } + string signature_size_string = base::JoinString(signature_size_strings, ":"); + + test_utils::ScopedTempFile hash_file("hash.XXXXXX"), + metadata_hash_file("hash.XXXXXX"); + string delta_generator_path = GetBuildArtifactsPath("delta_generator"); + ASSERT_EQ(0, + System(base::StringPrintf( + "%s -in_file=%s -signature_size=%s -out_hash_file=%s " + "-out_metadata_hash_file=%s", + delta_generator_path.c_str(), + payload_path.c_str(), + signature_size_string.c_str(), + hash_file.path().c_str(), + metadata_hash_file.path().c_str()))); + + // Sign the hash with all private keys. + vector<test_utils::ScopedTempFile> sig_files, metadata_sig_files; + vector<string> sig_file_paths, metadata_sig_file_paths; + for (const auto& key_path : private_key_paths) { + brillo::Blob hash, signature; + ASSERT_TRUE(utils::ReadFile(hash_file.path(), &hash)); + ASSERT_TRUE(PayloadSigner::SignHash(hash, key_path, &signature)); + + test_utils::ScopedTempFile sig_file("signature.XXXXXX"); + ASSERT_TRUE(test_utils::WriteFileVector(sig_file.path(), signature)); + sig_file_paths.push_back(sig_file.path()); + sig_files.push_back(std::move(sig_file)); + + brillo::Blob metadata_hash, metadata_signature; + ASSERT_TRUE(utils::ReadFile(metadata_hash_file.path(), &metadata_hash)); + ASSERT_TRUE( + PayloadSigner::SignHash(metadata_hash, key_path, &metadata_signature)); + + test_utils::ScopedTempFile metadata_sig_file("signature.XXXXXX"); + ASSERT_TRUE(test_utils::WriteFileVector(metadata_sig_file.path(), + metadata_signature)); + + metadata_sig_file_paths.push_back(metadata_sig_file.path()); + metadata_sig_files.push_back(std::move(metadata_sig_file)); + } + string sig_files_string = base::JoinString(sig_file_paths, ":"); + string metadata_sig_files_string = + base::JoinString(metadata_sig_file_paths, ":"); + + // Add the signature to the payload. + ASSERT_EQ(0, + System(base::StringPrintf("%s --signature_size=%s -in_file=%s " + "-payload_signature_file=%s " + "-metadata_signature_file=%s " + "-out_file=%s", + delta_generator_path.c_str(), + signature_size_string.c_str(), + payload_path.c_str(), + sig_files_string.c_str(), + metadata_sig_files_string.c_str(), + payload_path.c_str()))); + + int verify_result = System(base::StringPrintf("%s -in_file=%s -public_key=%s", + delta_generator_path.c_str(), + payload_path.c_str(), + public_key_path.c_str())); + + if (verification_success) { + ASSERT_EQ(0, verify_result); + } else { + ASSERT_NE(0, verify_result); + } } static void SignGeneratedShellPayload(SignatureTest signature_test, const string& payload_path) { - string private_key_path = GetBuildArtifactsPath(kUnittestPrivateKeyPath); + vector<SignatureTest> supported_test = { + kSignatureGeneratedShell, + kSignatureGeneratedShellBadKey, + kSignatureGeneratedShellECKey, + kSignatureGeneratedShellRotateCl1, + kSignatureGeneratedShellRotateCl2, + }; + ASSERT_TRUE(std::find(supported_test.begin(), + supported_test.end(), + signature_test) != supported_test.end()); + + string private_key_path; if (signature_test == kSignatureGeneratedShellBadKey) { ASSERT_TRUE(utils::MakeTempFile("key.XXXXXX", &private_key_path, nullptr)); + } else if (signature_test == kSignatureGeneratedShellECKey) { + private_key_path = GetBuildArtifactsPath(kUnittestPrivateKeyECPath); } else { - ASSERT_TRUE(signature_test == kSignatureGeneratedShell || - signature_test == kSignatureGeneratedShellRotateCl1 || - signature_test == kSignatureGeneratedShellRotateCl2); + private_key_path = GetBuildArtifactsPath(kUnittestPrivateKeyPath); } ScopedPathUnlinker key_unlinker(private_key_path); key_unlinker.set_should_remove(signature_test == kSignatureGeneratedShellBadKey); + // Generates a new private key that will not match the public key. if (signature_test == kSignatureGeneratedShellBadKey) { LOG(INFO) << "Generating a mismatched private key."; @@ -246,78 +369,26 @@ static void SignGeneratedShellPayload(SignatureTest signature_test, fclose(fprikey); RSA_free(rsa); } - int signature_size = GetSignatureSize(private_key_path); - test_utils::ScopedTempFile payload_hash_file("hash.XXXXXX"), - metadata_hash_file("hash.XXXXXX"); - string signature_size_string; - if (signature_test == kSignatureGeneratedShellRotateCl1 || - signature_test == kSignatureGeneratedShellRotateCl2) - signature_size_string = - base::StringPrintf("%d:%d", signature_size, signature_size); - else - signature_size_string = base::StringPrintf("%d", signature_size); - string delta_generator_path = GetBuildArtifactsPath("delta_generator"); - ASSERT_EQ(0, - System(base::StringPrintf( - "%s -in_file=%s -signature_size=%s -out_hash_file=%s " - "-out_metadata_hash_file=%s", - delta_generator_path.c_str(), - payload_path.c_str(), - signature_size_string.c_str(), - payload_hash_file.path().c_str(), - metadata_hash_file.path().c_str()))); - // Sign the payload hash. - test_utils::ScopedTempFile payload_signature_file("signature.XXXXXX"); - SignHashToFile(payload_hash_file.path(), - payload_signature_file.path(), - private_key_path); - string payload_sig_files = payload_signature_file.path(); - // Sign the metadata hash. - test_utils::ScopedTempFile metadata_signature_file("signature.XXXXXX"); - SignHashToFile(metadata_hash_file.path(), - metadata_signature_file.path(), - private_key_path); - string metadata_sig_files = metadata_signature_file.path(); - - test_utils::ScopedTempFile payload_signature_file2("signature.XXXXXX"); - test_utils::ScopedTempFile metadata_signature_file2("signature.XXXXXX"); + vector<string> private_key_paths = {private_key_path}; if (signature_test == kSignatureGeneratedShellRotateCl1 || signature_test == kSignatureGeneratedShellRotateCl2) { - SignHashToFile(payload_hash_file.path(), - payload_signature_file2.path(), - GetBuildArtifactsPath(kUnittestPrivateKey2Path)); - SignHashToFile(metadata_hash_file.path(), - metadata_signature_file2.path(), - GetBuildArtifactsPath(kUnittestPrivateKey2Path)); - // Append second sig file to first path - payload_sig_files += ":" + payload_signature_file2.path(); - metadata_sig_files += ":" + metadata_signature_file2.path(); + private_key_paths.push_back( + GetBuildArtifactsPath(kUnittestPrivateKey2Path)); } - ASSERT_EQ( - 0, - System(base::StringPrintf("%s -in_file=%s -payload_signature_file=%s " - "-metadata_signature_file=%s -out_file=%s", - delta_generator_path.c_str(), - payload_path.c_str(), - payload_sig_files.c_str(), - metadata_sig_files.c_str(), - payload_path.c_str()))); - int verify_result = System(base::StringPrintf( - "%s -in_file=%s -public_key=%s -public_key_version=%d", - delta_generator_path.c_str(), - payload_path.c_str(), - (signature_test == kSignatureGeneratedShellRotateCl2 - ? GetBuildArtifactsPath(kUnittestPublicKey2Path) - : GetBuildArtifactsPath(kUnittestPublicKeyPath)) - .c_str(), - signature_test == kSignatureGeneratedShellRotateCl2 ? 2 : 1)); - if (signature_test == kSignatureGeneratedShellBadKey) { - ASSERT_NE(0, verify_result); + std::string public_key; + if (signature_test == kSignatureGeneratedShellRotateCl2) { + public_key = GetBuildArtifactsPath(kUnittestPublicKey2Path); + } else if (signature_test == kSignatureGeneratedShellECKey) { + public_key = GetBuildArtifactsPath(kUnittestPublicKeyECPath); } else { - ASSERT_EQ(0, verify_result); + public_key = GetBuildArtifactsPath(kUnittestPublicKeyPath); } + + bool verification_success = signature_test != kSignatureGeneratedShellBadKey; + SignGeneratedShellPayloadWithKeys( + payload_path, private_key_paths, public_key, verification_success); } static void GenerateDeltaFile(bool full_kernel, @@ -549,8 +620,9 @@ static void GenerateDeltaFile(bool full_kernel, if (signature_test == kSignatureGeneratedPlaceholder || signature_test == kSignatureGeneratedPlaceholderMismatch) { - int signature_size = - GetSignatureSize(GetBuildArtifactsPath(kUnittestPrivateKeyPath)); + size_t signature_size; + ASSERT_TRUE(PayloadSigner::GetMaximumSignatureSize( + GetBuildArtifactsPath(kUnittestPrivateKeyPath), &signature_size)); LOG(INFO) << "Inserting placeholder signature."; ASSERT_TRUE(InsertSignaturePlaceholder( signature_size, state->delta_path, &state->metadata_size)); @@ -573,6 +645,7 @@ static void GenerateDeltaFile(bool full_kernel, LOG(INFO) << "Signing payload."; SignGeneratedPayload(state->delta_path, &state->metadata_size); } else if (signature_test == kSignatureGeneratedShell || + signature_test == kSignatureGeneratedShellECKey || signature_test == kSignatureGeneratedShellBadKey || signature_test == kSignatureGeneratedShellRotateCl1 || signature_test == kSignatureGeneratedShellRotateCl2) { @@ -617,15 +690,16 @@ static void ApplyDeltaFile(bool full_kernel, EXPECT_EQ(2, sigs_message.signatures_size()); else EXPECT_EQ(1, sigs_message.signatures_size()); - const Signatures_Signature& signature = sigs_message.signatures(0); - EXPECT_EQ(1U, signature.version()); + const Signatures::Signature& signature = sigs_message.signatures(0); - uint64_t expected_sig_data_length = 0; vector<string> key_paths{GetBuildArtifactsPath(kUnittestPrivateKeyPath)}; - if (signature_test == kSignatureGeneratedShellRotateCl1 || - signature_test == kSignatureGeneratedShellRotateCl2) { + if (signature_test == kSignatureGeneratedShellECKey) { + key_paths = {GetBuildArtifactsPath(kUnittestPrivateKeyECPath)}; + } else if (signature_test == kSignatureGeneratedShellRotateCl1 || + signature_test == kSignatureGeneratedShellRotateCl2) { key_paths.push_back(GetBuildArtifactsPath(kUnittestPrivateKey2Path)); } + uint64_t expected_sig_data_length = 0; EXPECT_TRUE(PayloadSigner::SignatureBlobLength( key_paths, &expected_sig_data_length)); EXPECT_EQ(expected_sig_data_length, manifest.signatures_size()); @@ -701,7 +775,12 @@ static void ApplyDeltaFile(bool full_kernel, .WillRepeatedly(Return(true)); EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignedSHA256Context, _)) .WillRepeatedly(Return(true)); - EXPECT_CALL(prefs, SetBoolean(kPrefsDynamicPartitionMetadataUpdated, _)) + EXPECT_CALL(prefs, SetString(kPrefsDynamicPartitionMetadataUpdated, _)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(prefs, + SetString(kPrefsManifestBytes, + testing::SizeIs(state->metadata_signature_size + + state->metadata_size))) .WillRepeatedly(Return(true)); if (op_hash_test == kValidOperationData && signature_test != kSignatureNone) { EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignatureBlob, _)) @@ -733,7 +812,9 @@ static void ApplyDeltaFile(bool full_kernel, ASSERT_TRUE(PayloadSigner::GetMetadataSignature( state->delta.data(), state->metadata_size, - GetBuildArtifactsPath(kUnittestPrivateKeyPath), + (signature_test == kSignatureGeneratedShellECKey) + ? GetBuildArtifactsPath(kUnittestPrivateKeyECPath) + : GetBuildArtifactsPath(kUnittestPrivateKeyPath), &install_plan->payloads[0].metadata_signature)); EXPECT_FALSE(install_plan->payloads[0].metadata_signature.empty()); @@ -744,9 +825,12 @@ static void ApplyDeltaFile(bool full_kernel, install_plan, &install_plan->payloads[0], false /* interactive */); - string public_key_path = GetBuildArtifactsPath(kUnittestPublicKeyPath); + string public_key_path = signature_test == kSignatureGeneratedShellECKey + ? GetBuildArtifactsPath(kUnittestPublicKeyECPath) + : GetBuildArtifactsPath(kUnittestPublicKeyPath); EXPECT_TRUE(utils::FileExists(public_key_path.c_str())); (*performer)->set_public_key_path(public_key_path); + (*performer)->set_update_certificates_path(""); EXPECT_EQ(static_cast<off_t>(state->image_size), HashCalculator::RawHashOfFile( @@ -948,13 +1032,13 @@ void DoOperationHashMismatchTest(OperationHashTest op_hash_test, delete performer; } -TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageTest) { +TEST_F(DeltaPerformerIntegrationTest, RunAsRootSmallImageTest) { DoSmallImageTest( false, false, -1, kSignatureGenerator, false, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootSmallImageSignaturePlaceholderTest) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignaturePlaceholderTest) { DoSmallImageTest(false, false, -1, @@ -963,8 +1047,8 @@ TEST(DeltaPerformerIntegrationTest, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootSmallImageSignaturePlaceholderMismatchTest) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignaturePlaceholderMismatchTest) { DeltaState state; GenerateDeltaFile(false, false, @@ -974,7 +1058,7 @@ TEST(DeltaPerformerIntegrationTest, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageChunksTest) { +TEST_F(DeltaPerformerIntegrationTest, RunAsRootSmallImageChunksTest) { DoSmallImageTest(false, false, kBlockSize, @@ -983,31 +1067,28 @@ TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageChunksTest) { kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootFullKernelSmallImageTest) { +TEST_F(DeltaPerformerIntegrationTest, RunAsRootFullKernelSmallImageTest) { DoSmallImageTest( true, false, -1, kSignatureGenerator, false, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootFullSmallImageTest) { - DoSmallImageTest(true, - true, - -1, - kSignatureGenerator, - true, - kFullPayloadMinorVersion); +TEST_F(DeltaPerformerIntegrationTest, RunAsRootFullSmallImageTest) { + DoSmallImageTest( + true, true, -1, kSignatureGenerator, true, kFullPayloadMinorVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignNoneTest) { +TEST_F(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignNoneTest) { DoSmallImageTest( false, false, -1, kSignatureNone, false, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedTest) { +TEST_F(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedTest) { DoSmallImageTest( false, false, -1, kSignatureGenerated, true, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellTest) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignGeneratedShellTest) { DoSmallImageTest(false, false, -1, @@ -1016,48 +1097,177 @@ TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellTest) { kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootSmallImageSignGeneratedShellBadKeyTest) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignGeneratedShellECKeyTest) { DoSmallImageTest(false, false, -1, - kSignatureGeneratedShellBadKey, + kSignatureGeneratedShellECKey, false, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootSmallImageSignGeneratedShellRotateCl1Test) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignGeneratedShellBadKeyTest) { DoSmallImageTest(false, false, -1, - kSignatureGeneratedShellRotateCl1, + kSignatureGeneratedShellBadKey, false, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootSmallImageSignGeneratedShellRotateCl2Test) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignGeneratedShellRotateCl1Test) { DoSmallImageTest(false, false, -1, - kSignatureGeneratedShellRotateCl2, + kSignatureGeneratedShellRotateCl1, false, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSourceOpsTest) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignGeneratedShellRotateCl2Test) { DoSmallImageTest(false, false, -1, - kSignatureGenerator, + kSignatureGeneratedShellRotateCl2, false, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootMandatoryOperationHashMismatchTest) { +TEST_F(DeltaPerformerIntegrationTest, RunAsRootSmallImageSourceOpsTest) { + DoSmallImageTest( + false, false, -1, kSignatureGenerator, false, kSourceMinorPayloadVersion); +} + +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootMandatoryOperationHashMismatchTest) { DoOperationHashMismatchTest(kInvalidOperationData, true); } +TEST_F(DeltaPerformerIntegrationTest, ValidatePerPartitionTimestampSuccess) { + // The Manifest we are validating. + DeltaArchiveManifest manifest; + + fake_hardware_.SetVersion("system", "5"); + fake_hardware_.SetVersion("product", "99"); + fake_hardware_.SetBuildTimestamp(1); + + manifest.set_minor_version(kFullPayloadMinorVersion); + manifest.set_max_timestamp(2); + AddPartition(&manifest, "system", 10); + AddPartition(&manifest, "product", 100); + + RunManifestValidation( + manifest, kMaxSupportedMajorPayloadVersion, ErrorCode::kSuccess); +} + +TEST_F(DeltaPerformerIntegrationTest, ValidatePerPartitionTimestampFailure) { + // The Manifest we are validating. + DeltaArchiveManifest manifest; + + fake_hardware_.SetVersion("system", "5"); + fake_hardware_.SetVersion("product", "99"); + fake_hardware_.SetBuildTimestamp(1); + + manifest.set_minor_version(kFullPayloadMinorVersion); + manifest.set_max_timestamp(2); + AddPartition(&manifest, "system", 10); + AddPartition(&manifest, "product", 98); + + RunManifestValidation(manifest, + kMaxSupportedMajorPayloadVersion, + ErrorCode::kPayloadTimestampError); +} + +TEST_F(DeltaPerformerIntegrationTest, + ValidatePerPartitionTimestampMissingTimestamp) { + // The Manifest we are validating. + DeltaArchiveManifest manifest; + + fake_hardware_.SetVersion("system", "5"); + fake_hardware_.SetVersion("product", "99"); + fake_hardware_.SetBuildTimestamp(1); + + manifest.set_minor_version(kFullPayloadMinorVersion); + manifest.set_max_timestamp(2); + AddPartition(&manifest, "system", 10); + { + auto& partition = *manifest.add_partitions(); + // For complete updates, missing timestamp should not trigger + // timestamp error. + partition.set_partition_name("product"); + } + + RunManifestValidation( + manifest, kMaxSupportedMajorPayloadVersion, ErrorCode::kSuccess); +} + +TEST_F(DeltaPerformerIntegrationTest, + ValidatePerPartitionTimestampPartialUpdatePass) { + fake_hardware_.SetVersion("system", "5"); + fake_hardware_.SetVersion("product", "99"); + + DeltaArchiveManifest manifest; + manifest.set_minor_version(kPartialUpdateMinorPayloadVersion); + manifest.set_partial_update(true); + AddPartition(&manifest, "product", 100); + RunManifestValidation( + manifest, kMaxSupportedMajorPayloadVersion, ErrorCode::kSuccess); +} + +TEST_F(DeltaPerformerIntegrationTest, + ValidatePerPartitionTimestampPartialUpdateDowngrade) { + fake_hardware_.SetVersion("system", "5"); + fake_hardware_.SetVersion("product", "99"); + + DeltaArchiveManifest manifest; + manifest.set_minor_version(kPartialUpdateMinorPayloadVersion); + manifest.set_partial_update(true); + AddPartition(&manifest, "product", 98); + RunManifestValidation(manifest, + kMaxSupportedMajorPayloadVersion, + ErrorCode::kPayloadTimestampError); +} + +TEST_F(DeltaPerformerIntegrationTest, + ValidatePerPartitionTimestampPartialUpdateMissingVersion) { + fake_hardware_.SetVersion("system", "5"); + fake_hardware_.SetVersion("product", "99"); + + DeltaArchiveManifest manifest; + manifest.set_minor_version(kPartialUpdateMinorPayloadVersion); + manifest.set_partial_update(true); + { + auto& partition = *manifest.add_partitions(); + // For partial updates, missing timestamp should trigger an error + partition.set_partition_name("product"); + // has_version() == false. + } + RunManifestValidation(manifest, + kMaxSupportedMajorPayloadVersion, + ErrorCode::kDownloadManifestParseError); +} + +TEST_F(DeltaPerformerIntegrationTest, + ValidatePerPartitionTimestampPartialUpdateEmptyVersion) { + fake_hardware_.SetVersion("system", "5"); + fake_hardware_.SetVersion("product", "99"); + + DeltaArchiveManifest manifest; + manifest.set_minor_version(kPartialUpdateMinorPayloadVersion); + manifest.set_partial_update(true); + { + auto& partition = *manifest.add_partitions(); + // For partial updates, invalid timestamp should trigger an error + partition.set_partition_name("product"); + partition.set_version("something"); + } + RunManifestValidation(manifest, + kMaxSupportedMajorPayloadVersion, + ErrorCode::kDownloadManifestParseError); +} + } // namespace chromeos_update_engine diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc index 39011957..449201ce 100644 --- a/payload_consumer/delta_performer_unittest.cc +++ b/payload_consumer/delta_performer_unittest.cc @@ -36,9 +36,11 @@ #include <gtest/gtest.h> #include "update_engine/common/constants.h" +#include "update_engine/common/error_code.h" #include "update_engine/common/fake_boot_control.h" #include "update_engine/common/fake_hardware.h" #include "update_engine/common/fake_prefs.h" +#include "update_engine/common/hardware_interface.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" #include "update_engine/payload_consumer/fake_file_descriptor.h" @@ -161,6 +163,11 @@ class DeltaPerformerTest : public ::testing::Test { install_plan_.target_slot = 1; EXPECT_CALL(mock_delegate_, ShouldCancel(_)) .WillRepeatedly(testing::Return(false)); + performer_.set_update_certificates_path(""); + // Set the public key corresponding to the unittest private key. + string public_key_path = GetBuildArtifactsPath(kUnittestPublicKeyPath); + EXPECT_TRUE(utils::FileExists(public_key_path.c_str())); + performer_.set_public_key_path(public_key_path); } // Test helper placed where it can easily be friended from DeltaPerformer. @@ -179,19 +186,22 @@ class DeltaPerformerTest : public ::testing::Test { brillo::Blob GeneratePayload(const brillo::Blob& blob_data, const vector<AnnotatedOperation>& aops, - bool sign_payload) { + bool sign_payload, + PartitionConfig* old_part = nullptr) { return GeneratePayload(blob_data, aops, sign_payload, kMaxSupportedMajorPayloadVersion, - kMaxSupportedMinorPayloadVersion); + kMaxSupportedMinorPayloadVersion, + old_part); } brillo::Blob GeneratePayload(const brillo::Blob& blob_data, const vector<AnnotatedOperation>& aops, bool sign_payload, uint64_t major_version, - uint32_t minor_version) { + uint32_t minor_version, + PartitionConfig* old_part = nullptr) { test_utils::ScopedTempFile blob_file("Blob-XXXXXX"); EXPECT_TRUE(test_utils::WriteFileVector(blob_file.path(), blob_data)); @@ -202,24 +212,29 @@ class DeltaPerformerTest : public ::testing::Test { PayloadFile payload; EXPECT_TRUE(payload.Init(config)); - PartitionConfig old_part(kPartitionNameRoot); + std::unique_ptr<PartitionConfig> old_part_uptr; + if (!old_part) { + old_part_uptr = std::make_unique<PartitionConfig>(kPartitionNameRoot); + old_part = old_part_uptr.get(); + } if (minor_version != kFullPayloadMinorVersion) { // When generating a delta payload we need to include the old partition // information to mark it as a delta payload. - old_part.path = "/dev/null"; - old_part.size = 0; + if (old_part->path.empty()) { + old_part->path = "/dev/null"; + } } PartitionConfig new_part(kPartitionNameRoot); new_part.path = "/dev/zero"; new_part.size = 1234; - payload.AddPartition(old_part, new_part, aops); + payload.AddPartition(*old_part, new_part, aops, {}); // We include a kernel partition without operations. - old_part.name = kPartitionNameKernel; + old_part->name = kPartitionNameKernel; new_part.name = kPartitionNameKernel; new_part.size = 0; - payload.AddPartition(old_part, new_part, {}); + payload.AddPartition(*old_part, new_part, {}, {}); test_utils::ScopedTempFile payload_file("Payload-XXXXXX"); string private_key = @@ -235,7 +250,8 @@ class DeltaPerformerTest : public ::testing::Test { } brillo::Blob GenerateSourceCopyPayload(const brillo::Blob& copied_data, - bool add_hash) { + bool add_hash, + PartitionConfig* old_part = nullptr) { PayloadGenerationConfig config; const uint64_t kDefaultBlockSize = config.block_size; EXPECT_EQ(0U, copied_data.size() % kDefaultBlockSize); @@ -249,7 +265,7 @@ class DeltaPerformerTest : public ::testing::Test { if (add_hash) aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size()); - return GeneratePayload(brillo::Blob(), {aop}, false); + return GeneratePayload(brillo::Blob(), {aop}, false, old_part); } // Apply |payload_data| on partition specified in |source_path|. @@ -316,7 +332,7 @@ class DeltaPerformerTest : public ::testing::Test { // When filling in size in manifest, exclude the size of the 24-byte header. uint64_t size_in_manifest = htobe64(actual_metadata_size - 24); performer_.Write(&size_in_manifest, 8, &error_code); - uint32_t signature_size = htobe64(10); + auto signature_size = htobe64(10); bool result = performer_.Write(&signature_size, 4, &error_code); if (expected_metadata_size == actual_metadata_size || !hash_checks_mandatory) { @@ -389,12 +405,6 @@ class DeltaPerformerTest : public ::testing::Test { expected_error = ErrorCode::kSuccess; } - // Use the public key corresponding to the private key used above to - // sign the metadata. - string public_key_path = GetBuildArtifactsPath(kUnittestPublicKeyPath); - EXPECT_TRUE(utils::FileExists(public_key_path.c_str())); - performer_.set_public_key_path(public_key_path); - // Init actual_error with an invalid value so that we make sure // ParsePayloadMetadata properly populates it in all cases. actual_error = ErrorCode::kUmaReportedMax; @@ -581,11 +591,16 @@ TEST_F(DeltaPerformerTest, SourceCopyOperationTest) { EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash)); aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size()); - brillo::Blob payload_data = GeneratePayload(brillo::Blob(), {aop}, false); - test_utils::ScopedTempFile source("Source-XXXXXX"); EXPECT_TRUE(test_utils::WriteFileVector(source.path(), expected_data)); + PartitionConfig old_part(kPartitionNameRoot); + old_part.path = source.path(); + old_part.size = expected_data.size(); + + brillo::Blob payload_data = + GeneratePayload(brillo::Blob(), {aop}, false, &old_part); + EXPECT_EQ(expected_data, ApplyPayload(payload_data, source.path(), true)); } @@ -604,11 +619,16 @@ TEST_F(DeltaPerformerTest, PuffdiffOperationTest) { EXPECT_TRUE(HashCalculator::RawHashOfData(src, &src_hash)); aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size()); - brillo::Blob payload_data = GeneratePayload(puffdiff_payload, {aop}, false); - test_utils::ScopedTempFile source("Source-XXXXXX"); EXPECT_TRUE(test_utils::WriteFileVector(source.path(), src)); + PartitionConfig old_part(kPartitionNameRoot); + old_part.path = source.path(); + old_part.size = src.size(); + + brillo::Blob payload_data = + GeneratePayload(puffdiff_payload, {aop}, false, &old_part); + brillo::Blob dst(std::begin(dst_deflates), std::end(dst_deflates)); EXPECT_EQ(dst, ApplyPayload(payload_data, source.path(), true)); } @@ -627,11 +647,16 @@ TEST_F(DeltaPerformerTest, SourceHashMismatchTest) { EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash)); aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size()); - brillo::Blob payload_data = GeneratePayload(brillo::Blob(), {aop}, false); - test_utils::ScopedTempFile source("Source-XXXXXX"); EXPECT_TRUE(test_utils::WriteFileVector(source.path(), actual_data)); + PartitionConfig old_part(kPartitionNameRoot); + old_part.path = source.path(); + old_part.size = actual_data.size(); + + brillo::Blob payload_data = + GeneratePayload(brillo::Blob(), {aop}, false, &old_part); + EXPECT_EQ(actual_data, ApplyPayload(payload_data, source.path(), false)); } @@ -650,7 +675,12 @@ TEST_F(DeltaPerformerTest, ErrorCorrectionSourceCopyFallbackTest) { FakeFileDescriptor* fake_fec = SetFakeECCFile(kCopyOperationSize); brillo::Blob expected_data = FakeFileDescriptorData(kCopyOperationSize); - brillo::Blob payload_data = GenerateSourceCopyPayload(expected_data, true); + PartitionConfig old_part(kPartitionNameRoot); + old_part.path = source.path(); + old_part.size = invalid_data.size(); + + brillo::Blob payload_data = + GenerateSourceCopyPayload(expected_data, true, &old_part); EXPECT_EQ(expected_data, ApplyPayload(payload_data, source.path(), true)); // Verify that the fake_fec was actually used. EXPECT_EQ(1U, fake_fec->GetReadOps().size()); @@ -671,8 +701,13 @@ TEST_F(DeltaPerformerTest, ErrorCorrectionSourceCopyWhenNoHashFallbackTest) { // the expected. FakeFileDescriptor* fake_fec = SetFakeECCFile(kCopyOperationSize / 2); + PartitionConfig old_part(kPartitionNameRoot); + old_part.path = source.path(); + old_part.size = expected_data.size(); + // The payload operation doesn't include an operation hash. - brillo::Blob payload_data = GenerateSourceCopyPayload(expected_data, false); + brillo::Blob payload_data = + GenerateSourceCopyPayload(expected_data, false, &old_part); EXPECT_EQ(expected_data, ApplyPayload(payload_data, source.path(), true)); // Verify that the fake_fec was attempted to be used. Since the file // descriptor is shorter it can actually do more than one read to realize it @@ -866,6 +901,24 @@ TEST_F(DeltaPerformerTest, ValidateManifestDowngrade) { ErrorCode::kPayloadTimestampError); } +TEST_F(DeltaPerformerTest, ValidatePerPartitionTimestampSuccess) { + // The Manifest we are validating. + DeltaArchiveManifest manifest; + + manifest.set_minor_version(kFullPayloadMinorVersion); + manifest.set_max_timestamp(2); + fake_hardware_.SetBuildTimestamp(1); + auto& partition = *manifest.add_partitions(); + partition.set_version("10"); + partition.set_partition_name("system"); + fake_hardware_.SetVersion("system", "5"); + + RunManifestValidation(manifest, + kMaxSupportedMajorPayloadVersion, + InstallPayloadType::kFull, + ErrorCode::kSuccess); +} + TEST_F(DeltaPerformerTest, BrilloMetadataSignatureSizeTest) { unsigned int seed = time(nullptr); EXPECT_TRUE(performer_.Write(kDeltaMagic, sizeof(kDeltaMagic))); @@ -968,7 +1021,6 @@ TEST_F(DeltaPerformerTest, BrilloParsePayloadMetadataTest) { brillo::Blob payload_data = GeneratePayload( {}, {}, true, kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion); install_plan_.hash_checks_mandatory = true; - performer_.set_public_key_path(GetBuildArtifactsPath(kUnittestPublicKeyPath)); payload_.size = payload_data.size(); ErrorCode error; EXPECT_EQ(MetadataParseResult::kSuccess, diff --git a/payload_consumer/download_action.cc b/payload_consumer/download_action.cc index 45df5a92..ea998926 100644 --- a/payload_consumer/download_action.cc +++ b/payload_consumer/download_action.cc @@ -55,8 +55,7 @@ DownloadAction::DownloadAction(PrefsInterface* prefs, code_(ErrorCode::kSuccess), delegate_(nullptr), p2p_sharing_fd_(-1), - p2p_visible_(true) { -} + p2p_visible_(true) {} DownloadAction::~DownloadAction() {} @@ -203,18 +202,76 @@ void DownloadAction::PerformAction() { StartDownloading(); } +bool DownloadAction::LoadCachedManifest(int64_t manifest_size) { + std::string cached_manifest_bytes; + if (!prefs_->GetString(kPrefsManifestBytes, &cached_manifest_bytes) || + cached_manifest_bytes.size() <= 0) { + LOG(INFO) << "Cached Manifest data not found"; + return false; + } + if (static_cast<int64_t>(cached_manifest_bytes.size()) != manifest_size) { + LOG(WARNING) << "Cached metadata has unexpected size: " + << cached_manifest_bytes.size() << " vs. " << manifest_size; + return false; + } + + ErrorCode error; + const bool success = + delta_performer_->Write( + cached_manifest_bytes.data(), cached_manifest_bytes.size(), &error) && + delta_performer_->IsManifestValid(); + if (success) { + LOG(INFO) << "Successfully parsed cached manifest"; + } else { + // If parsing of cached data failed, fall back to fetch them using HTTP + LOG(WARNING) << "Cached manifest data fails to load, error code:" + << static_cast<int>(error) << "," << error; + } + return success; +} + void DownloadAction::StartDownloading() { download_active_ = true; http_fetcher_->ClearRanges(); + + if (writer_ && writer_ != delta_performer_.get()) { + LOG(INFO) << "Using writer for test."; + } else { + delta_performer_.reset(new DeltaPerformer(prefs_, + boot_control_, + hardware_, + delegate_, + &install_plan_, + payload_, + interactive_)); + writer_ = delta_performer_.get(); + } + if (install_plan_.is_resume && payload_ == &install_plan_.payloads[resume_payload_index_]) { - // Resuming an update so fetch the update manifest metadata first. + // Resuming an update so parse the cached manifest first int64_t manifest_metadata_size = 0; int64_t manifest_signature_size = 0; prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size); prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size); - http_fetcher_->AddRange(base_offset_, - manifest_metadata_size + manifest_signature_size); + + // TODO(zhangkelvin) Add unittest for success and fallback route + if (!LoadCachedManifest(manifest_metadata_size + manifest_signature_size)) { + if (delta_performer_) { + // Create a new DeltaPerformer to reset all its state + delta_performer_ = std::make_unique<DeltaPerformer>(prefs_, + boot_control_, + hardware_, + delegate_, + &install_plan_, + payload_, + interactive_); + writer_ = delta_performer_.get(); + } + http_fetcher_->AddRange(base_offset_, + manifest_metadata_size + manifest_signature_size); + } + // If there're remaining unprocessed data blobs, fetch them. Be careful not // to request data beyond the end of the payload to avoid 416 HTTP response // error codes. @@ -238,18 +295,6 @@ void DownloadAction::StartDownloading() { } } - if (writer_ && writer_ != delta_performer_.get()) { - LOG(INFO) << "Using writer for test."; - } else { - delta_performer_.reset(new DeltaPerformer(prefs_, - boot_control_, - hardware_, - delegate_, - &install_plan_, - payload_, - interactive_)); - writer_ = delta_performer_.get(); - } if (system_state_ != nullptr) { const PayloadStateInterface* payload_state = system_state_->payload_state(); string file_id = utils::CalculateP2PFileId(payload_->hash, payload_->size); diff --git a/payload_consumer/download_action.h b/payload_consumer/download_action.h index 1777e22f..69284438 100644 --- a/payload_consumer/download_action.h +++ b/payload_consumer/download_action.h @@ -124,19 +124,20 @@ class DownloadAction : public InstallPlanAction, public HttpFetcherDelegate { bool SetupP2PSharingFd(); // Writes |length| bytes of payload from |data| into |file_offset| - // of the p2p file. Also does sanity checks; for example ensures we + // of the p2p file. Also does validation checks; for example ensures we // don't end up with a file with holes in it. // // This method does nothing if SetupP2PSharingFd() hasn't been // called or if CloseP2PSharingFd() has been called. void WriteToP2PFile(const void* data, size_t length, off_t file_offset); + // Attempt to load cached manifest data from prefs + // return true on success, false otherwise. + bool LoadCachedManifest(int64_t manifest_size); + // Start downloading the current payload using delta_performer. void StartDownloading(); - // The InstallPlan passed in - InstallPlan install_plan_; - // Pointer to the current payload in install_plan_.payloads. InstallPlan::Payload* payload_{nullptr}; diff --git a/payload_consumer/download_action_android_unittest.cc b/payload_consumer/download_action_android_unittest.cc new file mode 100644 index 00000000..f78845f5 --- /dev/null +++ b/payload_consumer/download_action_android_unittest.cc @@ -0,0 +1,90 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "common/mock_action_processor.h" +#include <gmock/gmock-actions.h> +#include <gmock/gmock-function-mocker.h> +#include <gmock/gmock-spec-builders.h> + +#include "payload_consumer/install_plan.h" +#include "update_engine/common/action_pipe.h" +#include "update_engine/common/boot_control_stub.h" +#include "update_engine/common/constants.h" +#include "update_engine/common/mock_http_fetcher.h" +#include "update_engine/common/mock_prefs.h" +#include "update_engine/common/test_utils.h" +#include "update_engine/payload_consumer/download_action.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <memory> + +namespace chromeos_update_engine { +using testing::_; +using testing::DoAll; +using testing::Return; +using testing::SetArgPointee; + +class DownloadActionTest : public ::testing::Test { + public: + static constexpr int64_t METADATA_SIZE = 1024; + static constexpr int64_t SIGNATURE_SIZE = 256; + std::shared_ptr<ActionPipe<InstallPlan>> action_pipe{ + new ActionPipe<InstallPlan>()}; +}; + +TEST_F(DownloadActionTest, CacheManifestInvalid) { + std::string data(METADATA_SIZE + SIGNATURE_SIZE, '-'); + MockPrefs prefs; + EXPECT_CALL(prefs, GetInt64(kPrefsUpdateStatePayloadIndex, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(0L), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsManifestMetadataSize, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(METADATA_SIZE), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsManifestSignatureSize, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(SIGNATURE_SIZE), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsUpdateStateNextDataOffset, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(0L), Return(true))); + EXPECT_CALL(prefs, GetString(kPrefsManifestBytes, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(data), Return(true))); + + BootControlStub boot_control; + MockHttpFetcher* http_fetcher = + new MockHttpFetcher(data.data(), data.size(), nullptr); + http_fetcher->set_delay(false); + InstallPlan install_plan; + auto& payload = install_plan.payloads.emplace_back(); + install_plan.download_url = "http://fake_url.invalid"; + payload.size = data.size(); + payload.payload_urls.emplace_back("http://fake_url.invalid"); + install_plan.is_resume = true; + action_pipe->set_contents(install_plan); + + // takes ownership of passed in HttpFetcher + auto download_action = + std::make_unique<DownloadAction>(&prefs, + &boot_control, + nullptr, + nullptr, + http_fetcher, + false /* interactive */); + download_action->set_in_pipe(action_pipe); + MockActionProcessor mock_processor; + download_action->SetProcessor(&mock_processor); + download_action->PerformAction(); + ASSERT_EQ(download_action->http_fetcher()->GetBytesDownloaded(), data.size()); +} + +} // namespace chromeos_update_engine diff --git a/payload_consumer/filesystem_verifier_action.cc b/payload_consumer/filesystem_verifier_action.cc index f9e7f81c..61917ea5 100644 --- a/payload_consumer/filesystem_verifier_action.cc +++ b/payload_consumer/filesystem_verifier_action.cc @@ -28,6 +28,7 @@ #include <base/bind.h> #include <brillo/data_encoding.h> #include <brillo/streams/file_stream.h> +#include <base/strings/string_util.h> #include "update_engine/common/utils.h" @@ -77,11 +78,32 @@ void FilesystemVerifierAction::Cleanup(ErrorCode code) { return; if (code == ErrorCode::kSuccess && HasOutputPipe()) SetOutputObject(install_plan_); + UpdateProgress(1.0); processor_->ActionComplete(this, code); } +void FilesystemVerifierAction::UpdateProgress(double progress) { + if (delegate_ != nullptr) { + delegate_->OnVerifyProgressUpdate(progress); + } +} + void FilesystemVerifierAction::StartPartitionHashing() { if (partition_index_ == install_plan_.partitions.size()) { + if (!install_plan_.untouched_dynamic_partitions.empty()) { + LOG(INFO) << "Verifying extents of untouched dynamic partitions [" + << base::JoinString(install_plan_.untouched_dynamic_partitions, + ", ") + << "]"; + if (!dynamic_control_->VerifyExtentsForUntouchedPartitions( + install_plan_.source_slot, + install_plan_.target_slot, + install_plan_.untouched_dynamic_partitions)) { + Cleanup(ErrorCode::kFilesystemVerifierError); + return; + } + } + Cleanup(ErrorCode::kSuccess); return; } @@ -188,7 +210,6 @@ void FilesystemVerifierAction::OnReadDoneCallback(size_t bytes_read) { Cleanup(ErrorCode::kError); return; } - if (bytes_read == 0) { LOG(ERROR) << "Failed to read the remaining " << partition_size_ - offset_ << " bytes from partition " @@ -203,6 +224,13 @@ void FilesystemVerifierAction::OnReadDoneCallback(size_t bytes_read) { 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 (verifier_step_ == VerifierStep::kVerifyTargetHash && install_plan_.write_verity) { if (!verity_writer_->Update(offset_, buffer_.data(), bytes_read)) { diff --git a/payload_consumer/filesystem_verifier_action.h b/payload_consumer/filesystem_verifier_action.h index 83d66687..6a8823a5 100644 --- a/payload_consumer/filesystem_verifier_action.h +++ b/payload_consumer/filesystem_verifier_action.h @@ -49,15 +49,34 @@ enum class VerifierStep { kVerifySourceHash, }; +class FilesystemVerifyDelegate { + public: + virtual ~FilesystemVerifyDelegate() = default; + virtual void OnVerifyProgressUpdate(double progress) = 0; +}; + class FilesystemVerifierAction : public InstallPlanAction { public: - FilesystemVerifierAction() - : verity_writer_(verity_writer::CreateVerityWriter()) {} + explicit FilesystemVerifierAction( + DynamicPartitionControlInterface* dynamic_control) + : verity_writer_(verity_writer::CreateVerityWriter()), + dynamic_control_(dynamic_control) { + CHECK(dynamic_control_); + } + ~FilesystemVerifierAction() override = default; void PerformAction() override; void TerminateProcessing() override; + // Used for listening to progress updates + void set_delegate(FilesystemVerifyDelegate* delegate) { + this->delegate_ = delegate; + } + [[nodiscard]] FilesystemVerifyDelegate* get_delegate() const { + return this->delegate_; + } + // Debugging/logging static std::string StaticType() { return "FilesystemVerifierAction"; } std::string Type() const override { return StaticType(); } @@ -85,6 +104,9 @@ class FilesystemVerifierAction : public InstallPlanAction { // true if TerminateProcessing() was called. void Cleanup(ErrorCode code); + // Invoke delegate callback to report progress, if delegate is not null + void UpdateProgress(double progress); + // The type of the partition that we are verifying. VerifierStep verifier_step_ = VerifierStep::kVerifyTargetHash; @@ -100,15 +122,15 @@ class FilesystemVerifierAction : public InstallPlanAction { bool cancelled_{false}; // true if the action has been cancelled. - // The install plan we're passed in via the input pipe. - InstallPlan install_plan_; - // Calculates the hash of the data. std::unique_ptr<HashCalculator> hasher_; // Write verity data of the current partition. std::unique_ptr<VerityWriterInterface> verity_writer_; + // Verifies the untouched dynamic partitions for partial updates. + DynamicPartitionControlInterface* dynamic_control_{nullptr}; + // Reads and hashes this many bytes from the head of the input stream. When // the partition starts to be hashed, this field is initialized from the // corresponding InstallPlan::Partition size which is the total size @@ -119,6 +141,9 @@ class FilesystemVerifierAction : public InstallPlanAction { // The byte offset that we are reading in the current partition. uint64_t offset_{0}; + // An observer that observes progress updates of this action. + FilesystemVerifyDelegate* delegate_{}; + DISALLOW_COPY_AND_ASSIGN(FilesystemVerifierAction); }; diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc index cb33404d..2971849c 100644 --- a/payload_consumer/filesystem_verifier_action_unittest.cc +++ b/payload_consumer/filesystem_verifier_action_unittest.cc @@ -27,6 +27,7 @@ #include <brillo/secure_blob.h> #include <gtest/gtest.h> +#include "update_engine/common/dynamic_partition_control_stub.h" #include "update_engine/common/hash_calculator.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" @@ -51,6 +52,7 @@ class FilesystemVerifierActionTest : public ::testing::Test { brillo::FakeMessageLoop loop_{nullptr}; ActionProcessor processor_; + DynamicPartitionControlStub dynamic_control_stub_; }; class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate { @@ -188,7 +190,8 @@ bool FilesystemVerifierActionTest::DoTest(bool terminate_early, void FilesystemVerifierActionTest::BuildActions( const InstallPlan& install_plan) { auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>(); - auto verifier_action = std::make_unique<FilesystemVerifierAction>(); + auto verifier_action = + std::make_unique<FilesystemVerifierAction>(&dynamic_control_stub_); auto collector_action = std::make_unique<ObjectCollectorAction<InstallPlan>>(); @@ -217,7 +220,8 @@ class FilesystemVerifierActionTest2Delegate : public ActionProcessorDelegate { }; TEST_F(FilesystemVerifierActionTest, MissingInputObjectTest) { - auto copier_action = std::make_unique<FilesystemVerifierAction>(); + auto copier_action = + std::make_unique<FilesystemVerifierAction>(&dynamic_control_stub_); auto collector_action = std::make_unique<ObjectCollectorAction<InstallPlan>>(); diff --git a/payload_consumer/install_plan.cc b/payload_consumer/install_plan.cc index 4638fbe0..c7ef7b20 100644 --- a/payload_consumer/install_plan.cc +++ b/payload_consumer/install_plan.cc @@ -87,12 +87,19 @@ void InstallPlan::Dump() const { base::StringPrintf(", system_version: %s", system_version.c_str()); } + string url_str = download_url; + if (base::StartsWith( + url_str, "fd://", base::CompareCase::INSENSITIVE_ASCII)) { + int fd = std::stoi(url_str.substr(strlen("fd://"))); + url_str = utils::GetFilePath(fd); + } + LOG(INFO) << "InstallPlan: " << (is_resume ? "resume" : "new_update") << version_str << ", source_slot: " << BootControlInterface::SlotName(source_slot) << ", target_slot: " << BootControlInterface::SlotName(target_slot) - << ", initial url: " << download_url << payloads_str - << partitions_str << ", hash_checks_mandatory: " + << ", initial url: " << url_str << payloads_str << partitions_str + << ", hash_checks_mandatory: " << utils::ToString(hash_checks_mandatory) << ", powerwash_required: " << utils::ToString(powerwash_required) << ", switch_slot_on_reboot: " @@ -105,7 +112,8 @@ void InstallPlan::Dump() const { bool InstallPlan::LoadPartitionsFromSlots(BootControlInterface* boot_control) { bool result = true; for (Partition& partition : partitions) { - if (source_slot != BootControlInterface::kInvalidSlot) { + if (source_slot != BootControlInterface::kInvalidSlot && + partition.source_size > 0) { result = boot_control->GetPartitionDevice( partition.name, source_slot, &partition.source_path) && result; diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h index 7a95ab43..f04c6504 100644 --- a/payload_consumer/install_plan.h +++ b/payload_consumer/install_plan.h @@ -158,6 +158,10 @@ struct InstallPlan { // If not blank, a base-64 encoded representation of the PEM-encoded // public key in the response. std::string public_key_rsa; + + // The name of dynamic partitions not included in the payload. Only used + // for partial updates. + std::vector<std::string> untouched_dynamic_partitions; }; class InstallPlanAction; @@ -195,9 +199,10 @@ class InstallPlanAction : public Action<InstallPlanAction> { typedef ActionTraits<InstallPlanAction>::InputObjectType InputObjectType; typedef ActionTraits<InstallPlanAction>::OutputObjectType OutputObjectType; - private: + protected: InstallPlan install_plan_; + private: DISALLOW_COPY_AND_ASSIGN(InstallPlanAction); }; diff --git a/payload_consumer/partition_update_generator_android.cc b/payload_consumer/partition_update_generator_android.cc new file mode 100644 index 00000000..25771e16 --- /dev/null +++ b/payload_consumer/partition_update_generator_android.cc @@ -0,0 +1,178 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_consumer/partition_update_generator_android.h" + +#include <filesystem> +#include <memory> +#include <utility> + +#include <android-base/properties.h> +#include <android-base/strings.h> +#include <base/logging.h> +#include <base/strings/string_split.h> + +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/hash_calculator.h" +#include "update_engine/common/utils.h" + +namespace chromeos_update_engine { + +PartitionUpdateGeneratorAndroid::PartitionUpdateGeneratorAndroid( + BootControlInterface* boot_control, size_t block_size) + : boot_control_(boot_control), block_size_(block_size) {} + +bool PartitionUpdateGeneratorAndroid:: + GenerateOperationsForPartitionsNotInPayload( + BootControlInterface::Slot source_slot, + BootControlInterface::Slot target_slot, + const std::set<std::string>& partitions_in_payload, + std::vector<PartitionUpdate>* update_list) { + auto ab_partitions = GetAbPartitionsOnDevice(); + if (ab_partitions.empty()) { + LOG(ERROR) << "Failed to load static a/b partitions"; + return false; + } + + std::vector<PartitionUpdate> partition_updates; + for (const auto& partition_name : ab_partitions) { + if (partitions_in_payload.find(partition_name) != + partitions_in_payload.end()) { + LOG(INFO) << partition_name << " has included in payload"; + continue; + } + bool is_source_dynamic = false; + std::string source_device; + + TEST_AND_RETURN_FALSE( + boot_control_->GetPartitionDevice(partition_name, + source_slot, + true, /* not_in_payload */ + &source_device, + &is_source_dynamic)); + bool is_target_dynamic = false; + std::string target_device; + TEST_AND_RETURN_FALSE(boot_control_->GetPartitionDevice( + partition_name, target_slot, true, &target_device, &is_target_dynamic)); + + if (is_source_dynamic || is_target_dynamic) { + if (is_source_dynamic != is_target_dynamic) { + LOG(ERROR) << "Partition " << partition_name << " is expected to be a" + << " static partition. source slot is " + << (is_source_dynamic ? "" : "not") + << " dynamic, and target slot " << target_slot << " is " + << (is_target_dynamic ? "" : "not") << " dynamic."; + return false; + } else { + continue; + } + } + + auto source_size = utils::FileSize(source_device); + auto target_size = utils::FileSize(target_device); + if (source_size == -1 || target_size == -1 || source_size != target_size || + source_size % block_size_ != 0) { + LOG(ERROR) << "Invalid partition size. source size " << source_size + << ", target size " << target_size; + return false; + } + + auto partition_update = CreatePartitionUpdate( + partition_name, source_device, target_device, source_size); + if (!partition_update.has_value()) { + LOG(ERROR) << "Failed to create partition update for " << partition_name; + return false; + } + partition_updates.push_back(partition_update.value()); + } + *update_list = std::move(partition_updates); + return true; +} + +std::vector<std::string> +PartitionUpdateGeneratorAndroid::GetAbPartitionsOnDevice() const { + auto partition_list_str = + android::base::GetProperty("ro.product.ab_ota_partitions", ""); + return base::SplitString(partition_list_str, + ",", + base::TRIM_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); +} + +std::optional<PartitionUpdate> +PartitionUpdateGeneratorAndroid::CreatePartitionUpdate( + const std::string& partition_name, + const std::string& source_device, + const std::string& target_device, + int64_t partition_size) { + PartitionUpdate partition_update; + partition_update.set_partition_name(partition_name); + auto old_partition_info = partition_update.mutable_old_partition_info(); + old_partition_info->set_size(partition_size); + + auto raw_hash = CalculateHashForPartition(source_device, partition_size); + if (!raw_hash.has_value()) { + LOG(ERROR) << "Failed to calculate hash for partition " << source_device + << " size: " << partition_size; + return {}; + } + old_partition_info->set_hash(raw_hash->data(), raw_hash->size()); + auto new_partition_info = partition_update.mutable_new_partition_info(); + new_partition_info->set_size(partition_size); + new_partition_info->set_hash(raw_hash->data(), raw_hash->size()); + + auto copy_operation = partition_update.add_operations(); + copy_operation->set_type(InstallOperation::SOURCE_COPY); + Extent copy_extent; + copy_extent.set_start_block(0); + copy_extent.set_num_blocks(partition_size / block_size_); + + *copy_operation->add_src_extents() = copy_extent; + *copy_operation->add_dst_extents() = copy_extent; + + return partition_update; +} + +std::optional<brillo::Blob> +PartitionUpdateGeneratorAndroid::CalculateHashForPartition( + const std::string& block_device, int64_t partition_size) { + // TODO(xunchang) compute the hash with ecc partitions first, the hashing + // behavior should match the one in SOURCE_COPY. Also, we don't have the + // correct hash for source partition. + // An alternative way is to verify the written bytes match the read bytes + // during filesystem verification. This could probably save us a read of + // partitions here. + brillo::Blob raw_hash; + if (HashCalculator::RawHashOfFile(block_device, partition_size, &raw_hash) != + partition_size) { + LOG(ERROR) << "Failed to calculate hash for " << block_device; + return std::nullopt; + } + + return raw_hash; +} + +namespace partition_update_generator { +std::unique_ptr<PartitionUpdateGeneratorInterface> Create( + BootControlInterface* boot_control, size_t block_size) { + CHECK(boot_control); + + return std::unique_ptr<PartitionUpdateGeneratorInterface>( + new PartitionUpdateGeneratorAndroid(boot_control, block_size)); +} +} // namespace partition_update_generator + +} // namespace chromeos_update_engine diff --git a/payload_consumer/partition_update_generator_android.h b/payload_consumer/partition_update_generator_android.h new file mode 100644 index 00000000..0330c99f --- /dev/null +++ b/payload_consumer/partition_update_generator_android.h @@ -0,0 +1,68 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_PARTITION_UPDATE_GENERATOR_ANDROID_H_ +#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PARTITION_UPDATE_GENERATOR_ANDROID_H_ + +#include <optional> +#include <set> +#include <string> +#include <vector> + +#include <brillo/secure_blob.h> +#include <gtest/gtest_prod.h> // for FRIEND_TEST + +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/payload_consumer/partition_update_generator_interface.h" + +namespace chromeos_update_engine { + +class PartitionUpdateGeneratorAndroid + : public PartitionUpdateGeneratorInterface { + public: + PartitionUpdateGeneratorAndroid(BootControlInterface* boot_control, + size_t block_size); + + bool GenerateOperationsForPartitionsNotInPayload( + BootControlInterface::Slot source_slot, + BootControlInterface::Slot target_slot, + const std::set<std::string>& partitions_in_payload, + std::vector<PartitionUpdate>* update_list) override; + virtual std::vector<std::string> GetAbPartitionsOnDevice() const; + + private: + friend class PartitionUpdateGeneratorAndroidTest; + FRIEND_TEST(PartitionUpdateGeneratorAndroidTest, GetStaticPartitions); + FRIEND_TEST(PartitionUpdateGeneratorAndroidTest, CreatePartitionUpdate); + + // Creates a PartitionUpdate object for a given partition to update from + // source to target. Returns std::nullopt on failure. + std::optional<PartitionUpdate> CreatePartitionUpdate( + const std::string& partition_name, + const std::string& source_device, + const std::string& target_device, + int64_t partition_size); + + std::optional<brillo::Blob> CalculateHashForPartition( + const std::string& block_device, int64_t partition_size); + + BootControlInterface* boot_control_; + size_t block_size_; +}; + +} // namespace chromeos_update_engine + +#endif diff --git a/payload_consumer/partition_update_generator_android_unittest.cc b/payload_consumer/partition_update_generator_android_unittest.cc new file mode 100644 index 00000000..86d025ed --- /dev/null +++ b/payload_consumer/partition_update_generator_android_unittest.cc @@ -0,0 +1,159 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_consumer/partition_update_generator_android.h" + +#include <map> +#include <memory> +#include <set> +#include <utility> +#include <vector> + +#include <android-base/strings.h> +#include <brillo/secure_blob.h> +#include <gtest/gtest.h> + +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/fake_boot_control.h" +#include "update_engine/common/hash_calculator.h" +#include "update_engine/common/test_utils.h" +#include "update_engine/common/utils.h" + +namespace chromeos_update_engine { + +class FakePartitionUpdateGenerator : public PartitionUpdateGeneratorAndroid { + public: + std::vector<std::string> GetAbPartitionsOnDevice() const { + return ab_partitions_; + } + using PartitionUpdateGeneratorAndroid::PartitionUpdateGeneratorAndroid; + std::vector<std::string> ab_partitions_; +}; + +class PartitionUpdateGeneratorAndroidTest : public ::testing::Test { + protected: + void SetUp() override { + ASSERT_TRUE(device_dir_.CreateUniqueTempDir()); + boot_control_ = std::make_unique<FakeBootControl>(); + ASSERT_TRUE(boot_control_); + boot_control_->SetNumSlots(2); + generator_ = std::make_unique<FakePartitionUpdateGenerator>( + boot_control_.get(), 4096); + ASSERT_TRUE(generator_); + } + + std::unique_ptr<FakePartitionUpdateGenerator> generator_; + std::unique_ptr<FakeBootControl> boot_control_; + + base::ScopedTempDir device_dir_; + std::map<std::string, std::string> device_map_; + + void SetUpBlockDevice(const std::map<std::string, std::string>& contents) { + std::set<std::string> partition_base_names; + for (const auto& [name, content] : contents) { + auto path = device_dir_.GetPath().value() + "/" + name; + ASSERT_TRUE( + utils::WriteFile(path.c_str(), content.data(), content.size())); + + if (android::base::EndsWith(name, "_a")) { + auto prefix = name.substr(0, name.size() - 2); + boot_control_->SetPartitionDevice(prefix, 0, path); + partition_base_names.emplace(prefix); + } else if (android::base::EndsWith(name, "_b")) { + auto prefix = name.substr(0, name.size() - 2); + boot_control_->SetPartitionDevice(prefix, 1, path); + partition_base_names.emplace(prefix); + } + device_map_[name] = std::move(path); + } + generator_->ab_partitions_ = {partition_base_names.begin(), + partition_base_names.end()}; + } + + void CheckPartitionUpdate(const std::string& name, + const std::string& content, + const PartitionUpdate& partition_update) { + ASSERT_EQ(name, partition_update.partition_name()); + + brillo::Blob out_hash; + ASSERT_TRUE(HashCalculator::RawHashOfBytes( + content.data(), content.size(), &out_hash)); + ASSERT_EQ(std::string(out_hash.begin(), out_hash.end()), + partition_update.old_partition_info().hash()); + ASSERT_EQ(std::string(out_hash.begin(), out_hash.end()), + partition_update.new_partition_info().hash()); + + ASSERT_EQ(1, partition_update.operations_size()); + const auto& operation = partition_update.operations(0); + ASSERT_EQ(InstallOperation::SOURCE_COPY, operation.type()); + + ASSERT_EQ(1, operation.src_extents_size()); + ASSERT_EQ(0u, operation.src_extents(0).start_block()); + ASSERT_EQ(content.size() / 4096, operation.src_extents(0).num_blocks()); + + ASSERT_EQ(1, operation.dst_extents_size()); + ASSERT_EQ(0u, operation.dst_extents(0).start_block()); + ASSERT_EQ(content.size() / 4096, operation.dst_extents(0).num_blocks()); + } +}; + +TEST_F(PartitionUpdateGeneratorAndroidTest, CreatePartitionUpdate) { + auto system_contents = std::string(4096 * 2, '1'); + auto boot_contents = std::string(4096 * 5, 'b'); + std::map<std::string, std::string> contents = { + {"system_a", system_contents}, + {"system_b", std::string(4096 * 2, 0)}, + {"boot_a", boot_contents}, + {"boot_b", std::string(4096 * 5, 0)}, + }; + SetUpBlockDevice(contents); + + auto system_partition_update = generator_->CreatePartitionUpdate( + "system", device_map_["system_a"], device_map_["system_b"], 4096 * 2); + ASSERT_TRUE(system_partition_update.has_value()); + CheckPartitionUpdate( + "system", system_contents, system_partition_update.value()); + + auto boot_partition_update = generator_->CreatePartitionUpdate( + "boot", device_map_["boot_a"], device_map_["boot_b"], 4096 * 5); + ASSERT_TRUE(boot_partition_update.has_value()); + CheckPartitionUpdate("boot", boot_contents, boot_partition_update.value()); +} + +TEST_F(PartitionUpdateGeneratorAndroidTest, GenerateOperations) { + auto system_contents = std::string(4096 * 10, '2'); + auto boot_contents = std::string(4096 * 5, 'b'); + std::map<std::string, std::string> contents = { + {"system_a", system_contents}, + {"system_b", std::string(4096 * 10, 0)}, + {"boot_a", boot_contents}, + {"boot_b", std::string(4096 * 5, 0)}, + {"vendor_a", ""}, + {"vendor_b", ""}, + {"persist", ""}, + }; + SetUpBlockDevice(contents); + + std::vector<PartitionUpdate> update_list; + ASSERT_TRUE(generator_->GenerateOperationsForPartitionsNotInPayload( + 0, 1, std::set<std::string>{"vendor"}, &update_list)); + + ASSERT_EQ(2u, update_list.size()); + CheckPartitionUpdate("boot", boot_contents, update_list[0]); + CheckPartitionUpdate("system", system_contents, update_list[1]); +} + +} // namespace chromeos_update_engine diff --git a/payload_consumer/partition_update_generator_interface.h b/payload_consumer/partition_update_generator_interface.h new file mode 100644 index 00000000..3fa3dfbc --- /dev/null +++ b/payload_consumer/partition_update_generator_interface.h @@ -0,0 +1,55 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_PARTITION_UPDATE_GENERATOR_INTERFACE_H_ +#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PARTITION_UPDATE_GENERATOR_INTERFACE_H_ + +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "update_engine/common/boot_control_interface.h" + +namespace chromeos_update_engine { +class PartitionUpdate; + +// This class parses the partitions that are not included in the payload of a +// partial A/B update. And it generates additional operations for these +// partitions to make the update complete. +class PartitionUpdateGeneratorInterface { + public: + virtual ~PartitionUpdateGeneratorInterface() = default; + + // Adds PartitionUpdate for partitions not included in the payload. For static + // partitions, it generates SOURCE_COPY operations to copy the bytes from the + // source slot to target slot. For dynamic partitions, it only calculates the + // partition hash for the filesystem verification later. + virtual bool GenerateOperationsForPartitionsNotInPayload( + BootControlInterface::Slot source_slot, + BootControlInterface::Slot target_slot, + const std::set<std::string>& partitions_in_payload, + std::vector<PartitionUpdate>* update_list) = 0; +}; + +namespace partition_update_generator { +std::unique_ptr<PartitionUpdateGeneratorInterface> Create( + BootControlInterface* boot_control, size_t block_size); +} + +} // namespace chromeos_update_engine + +#endif diff --git a/payload_consumer/partition_update_generator_stub.cc b/payload_consumer/partition_update_generator_stub.cc new file mode 100644 index 00000000..cfbd5e14 --- /dev/null +++ b/payload_consumer/partition_update_generator_stub.cc @@ -0,0 +1,38 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_consumer/partition_update_generator_stub.h" + +#include <memory> + +namespace chromeos_update_engine { + +bool PartitionUpdateGeneratorStub::GenerateOperationsForPartitionsNotInPayload( + chromeos_update_engine::BootControlInterface::Slot source_slot, + chromeos_update_engine::BootControlInterface::Slot target_slot, + const std::set<std::string>& partitions_in_payload, + std::vector<PartitionUpdate>* update_list) { + return true; +} + +namespace partition_update_generator { +std::unique_ptr<PartitionUpdateGeneratorInterface> Create( + BootControlInterface* boot_control, size_t block_size) { + return std::make_unique<PartitionUpdateGeneratorStub>(); +} +} // namespace partition_update_generator + +} // namespace chromeos_update_engine diff --git a/payload_consumer/partition_update_generator_stub.h b/payload_consumer/partition_update_generator_stub.h new file mode 100644 index 00000000..282875ef --- /dev/null +++ b/payload_consumer/partition_update_generator_stub.h @@ -0,0 +1,40 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_PARTITION_UPDATE_GENERATOR_STUB_H_ +#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PARTITION_UPDATE_GENERATOR_STUB_H_ + +#include <set> +#include <string> +#include <vector> + +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/payload_consumer/partition_update_generator_interface.h" + +namespace chromeos_update_engine { +class PartitionUpdateGeneratorStub : public PartitionUpdateGeneratorInterface { + public: + PartitionUpdateGeneratorStub() = default; + bool GenerateOperationsForPartitionsNotInPayload( + BootControlInterface::Slot source_slot, + BootControlInterface::Slot target_slot, + const std::set<std::string>& partitions_in_payload, + std::vector<PartitionUpdate>* update_list) override; +}; + +} // namespace chromeos_update_engine + +#endif diff --git a/payload_consumer/payload_constants.cc b/payload_consumer/payload_constants.cc index 299bcfc1..663ab81f 100644 --- a/payload_consumer/payload_constants.cc +++ b/payload_consumer/payload_constants.cc @@ -33,9 +33,11 @@ const uint32_t kOpSrcHashMinorPayloadVersion = 3; const uint32_t kBrotliBsdiffMinorPayloadVersion = 4; const uint32_t kPuffdiffMinorPayloadVersion = 5; const uint32_t kVerityMinorPayloadVersion = 6; +const uint32_t kPartialUpdateMinorPayloadVersion = 7; const uint32_t kMinSupportedMinorPayloadVersion = kSourceMinorPayloadVersion; -const uint32_t kMaxSupportedMinorPayloadVersion = kVerityMinorPayloadVersion; +const uint32_t kMaxSupportedMinorPayloadVersion = + kPartialUpdateMinorPayloadVersion; const uint64_t kMaxPayloadHeaderSize = 24; @@ -44,7 +46,7 @@ const char kPartitionNameRoot[] = "root"; const char kDeltaMagic[4] = {'C', 'r', 'A', 'U'}; -const char* InstallOperationTypeName(InstallOperation_Type op_type) { +const char* InstallOperationTypeName(InstallOperation::Type op_type) { switch (op_type) { case InstallOperation::REPLACE: return "REPLACE"; diff --git a/payload_consumer/payload_constants.h b/payload_consumer/payload_constants.h index 888fa2a1..03647ee7 100644 --- a/payload_consumer/payload_constants.h +++ b/payload_consumer/payload_constants.h @@ -56,6 +56,9 @@ extern const uint32_t kPuffdiffMinorPayloadVersion; // The minor version that allows Verity hash tree and FEC generation. extern const uint32_t kVerityMinorPayloadVersion; +// The minor version that allows partial update, e.g. kernel only update. +extern const uint32_t kPartialUpdateMinorPayloadVersion; + // The minimum and maximum supported minor version. extern const uint32_t kMinSupportedMinorPayloadVersion; extern const uint32_t kMaxSupportedMinorPayloadVersion; @@ -77,7 +80,7 @@ extern const char kDeltaMagic[4]; const uint64_t kSparseHole = std::numeric_limits<uint64_t>::max(); // Return the name of the operation type. -const char* InstallOperationTypeName(InstallOperation_Type op_type); +const char* InstallOperationTypeName(InstallOperation::Type op_type); } // namespace chromeos_update_engine diff --git a/payload_consumer/payload_metadata.cc b/payload_consumer/payload_metadata.cc index b83001a2..2cb73eb1 100644 --- a/payload_consumer/payload_metadata.cc +++ b/payload_consumer/payload_metadata.cc @@ -18,8 +18,10 @@ #include <endian.h> +#include <base/strings/stringprintf.h> #include <brillo/data_encoding.h> +#include "update_engine/common/constants.h" #include "update_engine/common/hash_calculator.h" #include "update_engine/common/utils.h" #include "update_engine/payload_consumer/payload_constants.h" @@ -54,7 +56,18 @@ MetadataParseResult PayloadMetadata::ParsePayloadHeader( // Validate the magic string. if (memcmp(payload.data(), kDeltaMagic, sizeof(kDeltaMagic)) != 0) { - LOG(ERROR) << "Bad payload format -- invalid delta magic."; + LOG(ERROR) << "Bad payload format -- invalid delta magic: " + << base::StringPrintf("%02x%02x%02x%02x", + payload[0], + payload[1], + payload[2], + payload[3]) + << " Expected: " + << base::StringPrintf("%02x%02x%02x%02x", + kDeltaMagic[0], + kDeltaMagic[1], + kDeltaMagic[2], + kDeltaMagic[3]); *error = ErrorCode::kDownloadInvalidMetadataMagicString; return MetadataParseResult::kError; } @@ -131,12 +144,16 @@ bool PayloadMetadata::GetManifest(const brillo::Blob& payload, ErrorCode PayloadMetadata::ValidateMetadataSignature( const brillo::Blob& payload, - const std::string& metadata_signature, - const std::string& pem_public_key) const { + const string& metadata_signature, + const PayloadVerifier& payload_verifier) const { if (payload.size() < metadata_size_ + metadata_signature_size_) return ErrorCode::kDownloadMetadataSignatureError; - brillo::Blob metadata_signature_blob, metadata_signature_protobuf_blob; + // A single signature in raw bytes. + brillo::Blob metadata_signature_blob; + // The serialized Signatures protobuf message stored in major version >=2 + // payload, it may contain multiple signatures. + string metadata_signature_protobuf; if (!metadata_signature.empty()) { // Convert base64-encoded signature to raw bytes. if (!brillo::data_encoding::Base64Decode(metadata_signature, @@ -146,49 +163,43 @@ ErrorCode PayloadMetadata::ValidateMetadataSignature( return ErrorCode::kDownloadMetadataSignatureError; } } else { - metadata_signature_protobuf_blob.assign( + metadata_signature_protobuf.assign( payload.begin() + metadata_size_, payload.begin() + metadata_size_ + metadata_signature_size_); } - if (metadata_signature_blob.empty() && - metadata_signature_protobuf_blob.empty()) { + if (metadata_signature_blob.empty() && metadata_signature_protobuf.empty()) { LOG(ERROR) << "Missing mandatory metadata signature in both Omaha " << "response and payload."; return ErrorCode::kDownloadMetadataSignatureMissingError; } - brillo::Blob calculated_metadata_hash; + brillo::Blob metadata_hash; if (!HashCalculator::RawHashOfBytes( - payload.data(), metadata_size_, &calculated_metadata_hash)) { + payload.data(), metadata_size_, &metadata_hash)) { LOG(ERROR) << "Unable to compute actual hash of manifest"; return ErrorCode::kDownloadMetadataSignatureVerificationError; } - PayloadVerifier::PadRSA2048SHA256Hash(&calculated_metadata_hash); - if (calculated_metadata_hash.empty()) { - LOG(ERROR) << "Computed actual hash of metadata is empty."; + if (metadata_hash.size() != kSHA256Size) { + LOG(ERROR) << "Computed actual hash of metadata has incorrect size: " + << metadata_hash.size(); return ErrorCode::kDownloadMetadataSignatureVerificationError; } if (!metadata_signature_blob.empty()) { - brillo::Blob expected_metadata_hash; - if (!PayloadVerifier::GetRawHashFromSignature( - metadata_signature_blob, pem_public_key, &expected_metadata_hash)) { - LOG(ERROR) << "Unable to compute expected hash from metadata signature"; - return ErrorCode::kDownloadMetadataSignatureError; - } - if (calculated_metadata_hash != expected_metadata_hash) { - LOG(ERROR) << "Manifest hash verification failed. Expected hash = "; - utils::HexDumpVector(expected_metadata_hash); - LOG(ERROR) << "Calculated hash = "; - utils::HexDumpVector(calculated_metadata_hash); + brillo::Blob decrypted_signature; + if (!payload_verifier.VerifyRawSignature( + metadata_signature_blob, metadata_hash, &decrypted_signature)) { + LOG(ERROR) << "Manifest hash verification failed. Decrypted hash = "; + utils::HexDumpVector(decrypted_signature); + LOG(ERROR) << "Calculated hash before padding = "; + utils::HexDumpVector(metadata_hash); return ErrorCode::kDownloadMetadataSignatureMismatch; } } else { - if (!PayloadVerifier::VerifySignature(metadata_signature_protobuf_blob, - pem_public_key, - calculated_metadata_hash)) { + if (!payload_verifier.VerifySignature(metadata_signature_protobuf, + metadata_hash)) { LOG(ERROR) << "Manifest hash verification failed."; return ErrorCode::kDownloadMetadataSignatureMismatch; } diff --git a/payload_consumer/payload_metadata.h b/payload_consumer/payload_metadata.h index be43c410..8b36f533 100644 --- a/payload_consumer/payload_metadata.h +++ b/payload_consumer/payload_metadata.h @@ -27,6 +27,7 @@ #include "update_engine/common/error_code.h" #include "update_engine/common/platform_constants.h" +#include "update_engine/payload_consumer/payload_verifier.h" #include "update_engine/update_metadata.pb.h" namespace chromeos_update_engine { @@ -62,12 +63,13 @@ class PayloadMetadata { // |metadata_signature| (if present) or the metadata signature in payload // itself (if present). Returns ErrorCode::kSuccess on match or a suitable // error code otherwise. This method must be called before any part of the - // metadata is parsed so that a man-in-the-middle attack on the SSL connection + // metadata is parsed so that an on-path attack on the SSL connection // to the payload server doesn't exploit any vulnerability in the code that // parses the protocol buffer. - ErrorCode ValidateMetadataSignature(const brillo::Blob& payload, - const std::string& metadata_signature, - const std::string& pem_public_key) const; + ErrorCode ValidateMetadataSignature( + const brillo::Blob& payload, + const std::string& metadata_signature, + const PayloadVerifier& payload_verifier) const; // Returns the major payload version. If the version was not yet parsed, // returns zero. diff --git a/payload_consumer/payload_verifier.cc b/payload_consumer/payload_verifier.cc index 2f7c133a..7fd2b8e6 100644 --- a/payload_consumer/payload_verifier.cc +++ b/payload_consumer/payload_verifier.cc @@ -16,13 +16,16 @@ #include "update_engine/payload_consumer/payload_verifier.h" +#include <utility> #include <vector> #include <base/logging.h> #include <openssl/pem.h> +#include "update_engine/common/constants.h" #include "update_engine/common/hash_calculator.h" #include "update_engine/common/utils.h" +#include "update_engine/payload_consumer/certificate_parser_interface.h" #include "update_engine/update_metadata.pb.h" using std::string; @@ -31,61 +34,73 @@ namespace chromeos_update_engine { namespace { -// The following is a standard PKCS1-v1_5 padding for SHA256 signatures, as -// defined in RFC3447. It is prepended to the actual signature (32 bytes) to -// form a sequence of 256 bytes (2048 bits) that is amenable to RSA signing. The -// padded hash will look as follows: +// The ASN.1 DigestInfo prefix for encoding SHA256 digest. The complete 51-byte +// DigestInfo consists of 19-byte SHA256_DIGEST_INFO_PREFIX and 32-byte SHA256 +// digest. // -// 0x00 0x01 0xff ... 0xff 0x00 ASN1HEADER SHA256HASH -// |--------------205-----------||----19----||----32----| -// -// where ASN1HEADER is the ASN.1 description of the signed data. The complete 51 -// bytes of actual data (i.e. the ASN.1 header complete with the hash) are -// packed as follows: -// -// SEQUENCE(2+49) { +// SEQUENCE(2+49) { // SEQUENCE(2+13) { -// OBJECT(2+9) id-sha256 -// NULL(2+0) +// OBJECT(2+9) id-sha256 +// NULL(2+0) // } // OCTET STRING(2+32) <actual signature bytes...> -// } -// clang-format off -const uint8_t kRSA2048SHA256Padding[] = { - // PKCS1-v1_5 padding - 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, - // ASN.1 header - 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, - 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20, +// } +const uint8_t kSHA256DigestInfoPrefix[] = { + 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20, }; -// clang-format on } // namespace -bool PayloadVerifier::VerifySignature(const brillo::Blob& signature_blob, - const string& pem_public_key, - const brillo::Blob& hash_data) { +std::unique_ptr<PayloadVerifier> PayloadVerifier::CreateInstance( + const std::string& pem_public_key) { + std::unique_ptr<BIO, decltype(&BIO_free)> bp( + BIO_new_mem_buf(pem_public_key.data(), pem_public_key.size()), BIO_free); + if (!bp) { + LOG(ERROR) << "Failed to read " << pem_public_key << " into buffer."; + return nullptr; + } + + auto pub_key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>( + PEM_read_bio_PUBKEY(bp.get(), nullptr, nullptr, nullptr), EVP_PKEY_free); + if (!pub_key) { + LOG(ERROR) << "Failed to parse the public key in: " << pem_public_key; + return nullptr; + } + + std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>> keys; + keys.emplace_back(std::move(pub_key)); + return std::unique_ptr<PayloadVerifier>(new PayloadVerifier(std::move(keys))); +} + +std::unique_ptr<PayloadVerifier> PayloadVerifier::CreateInstanceFromZipPath( + const std::string& certificate_zip_path) { + auto parser = CreateCertificateParser(); + if (!parser) { + LOG(ERROR) << "Failed to create certificate parser from " + << certificate_zip_path; + return nullptr; + } + + std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>> public_keys; + if (!parser->ReadPublicKeysFromCertificates(certificate_zip_path, + &public_keys) || + public_keys.empty()) { + LOG(ERROR) << "Failed to parse public keys in: " << certificate_zip_path; + return nullptr; + } + + return std::unique_ptr<PayloadVerifier>( + new PayloadVerifier(std::move(public_keys))); +} + +bool PayloadVerifier::VerifySignature( + const string& signature_proto, const brillo::Blob& sha256_hash_data) const { + TEST_AND_RETURN_FALSE(!public_keys_.empty()); + Signatures signatures; - LOG(INFO) << "signature blob size = " << signature_blob.size(); - TEST_AND_RETURN_FALSE( - signatures.ParseFromArray(signature_blob.data(), signature_blob.size())); + LOG(INFO) << "signature blob size = " << signature_proto.size(); + TEST_AND_RETURN_FALSE(signatures.ParseFromString(signature_proto)); if (!signatures.signatures_size()) { LOG(ERROR) << "No signatures stored in the blob."; @@ -95,41 +110,109 @@ bool PayloadVerifier::VerifySignature(const brillo::Blob& signature_blob, std::vector<brillo::Blob> tested_hashes; // Tries every signature in the signature blob. for (int i = 0; i < signatures.signatures_size(); i++) { - const Signatures_Signature& signature = signatures.signatures(i); - brillo::Blob sig_data(signature.data().begin(), signature.data().end()); - brillo::Blob sig_hash_data; - if (!GetRawHashFromSignature(sig_data, pem_public_key, &sig_hash_data)) - continue; + const Signatures::Signature& signature = signatures.signatures(i); + brillo::Blob sig_data; + if (signature.has_unpadded_signature_size()) { + TEST_AND_RETURN_FALSE(signature.unpadded_signature_size() <= + signature.data().size()); + LOG(INFO) << "Truncating the signature to its unpadded size: " + << signature.unpadded_signature_size() << "."; + sig_data.assign( + signature.data().begin(), + signature.data().begin() + signature.unpadded_signature_size()); + } else { + sig_data.assign(signature.data().begin(), signature.data().end()); + } - if (hash_data == sig_hash_data) { + brillo::Blob sig_hash_data; + if (VerifyRawSignature(sig_data, sha256_hash_data, &sig_hash_data)) { LOG(INFO) << "Verified correct signature " << i + 1 << " out of " << signatures.signatures_size() << " signatures."; return true; } - tested_hashes.push_back(sig_hash_data); + if (!sig_hash_data.empty()) { + tested_hashes.push_back(sig_hash_data); + } } LOG(ERROR) << "None of the " << signatures.signatures_size() - << " signatures is correct. Expected:"; - utils::HexDumpVector(hash_data); - LOG(ERROR) << "But found decrypted hashes:"; + << " signatures is correct. Expected hash before padding:"; + utils::HexDumpVector(sha256_hash_data); + LOG(ERROR) << "But found RSA decrypted hashes:"; for (const auto& sig_hash_data : tested_hashes) { utils::HexDumpVector(sig_hash_data); } return false; } -bool PayloadVerifier::GetRawHashFromSignature(const brillo::Blob& sig_data, - const string& pem_public_key, - brillo::Blob* out_hash_data) { +bool PayloadVerifier::VerifyRawSignature( + const brillo::Blob& sig_data, + const brillo::Blob& sha256_hash_data, + brillo::Blob* decrypted_sig_data) const { + TEST_AND_RETURN_FALSE(!public_keys_.empty()); + + for (const auto& public_key : public_keys_) { + int key_type = EVP_PKEY_id(public_key.get()); + if (key_type == EVP_PKEY_RSA) { + brillo::Blob sig_hash_data; + if (!GetRawHashFromSignature( + sig_data, public_key.get(), &sig_hash_data)) { + LOG(WARNING) + << "Failed to get the raw hash with RSA key. Trying other keys."; + continue; + } + + if (decrypted_sig_data != nullptr) { + *decrypted_sig_data = sig_hash_data; + } + + brillo::Blob padded_hash_data = sha256_hash_data; + TEST_AND_RETURN_FALSE( + PadRSASHA256Hash(&padded_hash_data, sig_hash_data.size())); + + if (padded_hash_data == sig_hash_data) { + return true; + } + } + + if (key_type == EVP_PKEY_EC) { + // TODO(b/158580694): Switch back to get0 version and remove manual + // freeing of the object once the bug is resolved or gale has been moved + // to informational. + EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(public_key.get()); + TEST_AND_RETURN_FALSE(ec_key != nullptr); + if (ECDSA_verify(0, + sha256_hash_data.data(), + sha256_hash_data.size(), + sig_data.data(), + sig_data.size(), + ec_key) == 1) { + EC_KEY_free(ec_key); + return true; + } + EC_KEY_free(ec_key); + } + + LOG(ERROR) << "Unsupported key type " << key_type; + return false; + } + LOG(INFO) << "Failed to verify the signature with " << public_keys_.size() + << " keys."; + return false; +} + +bool PayloadVerifier::GetRawHashFromSignature( + const brillo::Blob& sig_data, + const EVP_PKEY* public_key, + brillo::Blob* out_hash_data) const { + // TODO(b/158580694): Switch back to get0 version and remove manual freeing of + // the object once the bug is resolved or gale has been moved to + // informational. + // // The code below executes the equivalent of: // - // openssl rsautl -verify -pubin -inkey <(echo |pem_public_key|) + // openssl rsautl -verify -pubin -inkey <(echo pem_public_key) // -in |sig_data| -out |out_hash_data| - - BIO* bp = BIO_new_mem_buf(pem_public_key.data(), pem_public_key.size()); - char dummy_password[] = {' ', 0}; // Ensure no password is read from stdin. - RSA* rsa = PEM_read_bio_RSA_PUBKEY(bp, nullptr, nullptr, dummy_password); - BIO_free(bp); + RSA* rsa = EVP_PKEY_get1_RSA(const_cast<EVP_PKEY*>(public_key)); TEST_AND_RETURN_FALSE(rsa != nullptr); unsigned int keysize = RSA_size(rsa); @@ -151,13 +234,30 @@ bool PayloadVerifier::GetRawHashFromSignature(const brillo::Blob& sig_data, return true; } -bool PayloadVerifier::PadRSA2048SHA256Hash(brillo::Blob* hash) { - TEST_AND_RETURN_FALSE(hash->size() == 32); - hash->insert(hash->begin(), - reinterpret_cast<const char*>(kRSA2048SHA256Padding), - reinterpret_cast<const char*>(kRSA2048SHA256Padding + - sizeof(kRSA2048SHA256Padding))); - TEST_AND_RETURN_FALSE(hash->size() == 256); +bool PayloadVerifier::PadRSASHA256Hash(brillo::Blob* hash, size_t rsa_size) { + TEST_AND_RETURN_FALSE(hash->size() == kSHA256Size); + TEST_AND_RETURN_FALSE(rsa_size == 256 || rsa_size == 512); + + // The following is a standard PKCS1-v1_5 padding for SHA256 signatures, as + // defined in RFC3447 section 9.2. It is prepended to the actual signature + // (32 bytes) to form a sequence of 256|512 bytes (2048|4096 bits) that is + // amenable to RSA signing. The padded hash will look as follows: + // + // 0x00 0x01 0xff ... 0xff 0x00 ASN1HEADER SHA256HASH + // |-----------205|461----------||----19----||----32----| + size_t padding_string_size = + rsa_size - hash->size() - sizeof(kSHA256DigestInfoPrefix) - 3; + brillo::Blob padded_result = brillo::CombineBlobs({ + {0x00, 0x01}, + brillo::Blob(padding_string_size, 0xff), + {0x00}, + brillo::Blob(kSHA256DigestInfoPrefix, + kSHA256DigestInfoPrefix + sizeof(kSHA256DigestInfoPrefix)), + *hash, + }); + + *hash = std::move(padded_result); + TEST_AND_RETURN_FALSE(hash->size() == rsa_size); return true; } diff --git a/payload_consumer/payload_verifier.h b/payload_consumer/payload_verifier.h index ec23ef21..bc5231fa 100644 --- a/payload_consumer/payload_verifier.h +++ b/payload_consumer/payload_verifier.h @@ -17,47 +17,72 @@ #ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_VERIFIER_H_ #define UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_VERIFIER_H_ +#include <memory> #include <string> +#include <utility> +#include <vector> -#include <base/macros.h> #include <brillo/secure_blob.h> +#include <openssl/evp.h> #include "update_engine/update_metadata.pb.h" -// This class encapsulates methods used for payload signature verification. -// See payload_generator/payload_signer.h for payload signing. +// This class holds the public keys and implements methods used for payload +// signature verification. See payload_generator/payload_signer.h for payload +// signing. namespace chromeos_update_engine { class PayloadVerifier { public: - // Interprets |signature_blob| as a protocol buffer containing the Signatures - // message and decrypts each signature data using the |pem_public_key|. - // |pem_public_key| should be a PEM format RSA public key data. - // Returns whether *any* of the decrypted hashes matches the |hash_data|. - // In case of any error parsing the signatures or the public key, returns - // false. - static bool VerifySignature(const brillo::Blob& signature_blob, - const std::string& pem_public_key, - const brillo::Blob& hash_data); - - // Decrypts |sig_data| with the given |pem_public_key| and populates - // |out_hash_data| with the decoded raw hash. |pem_public_key| should be a PEM - // format RSA public key data. Returns true if successful, false otherwise. - static bool GetRawHashFromSignature(const brillo::Blob& sig_data, - const std::string& pem_public_key, - brillo::Blob* out_hash_data); - - // Pads a SHA256 hash so that it may be encrypted/signed with RSA2048 - // using the PKCS#1 v1.5 scheme. - // hash should be a pointer to vector of exactly 256 bits. The vector - // will be modified in place and will result in having a length of - // 2048 bits. Returns true on success, false otherwise. - static bool PadRSA2048SHA256Hash(brillo::Blob* hash); + // Pads a SHA256 hash so that it may be encrypted/signed with RSA2048 or + // RSA4096 using the PKCS#1 v1.5 scheme. + // hash should be a pointer to vector of exactly 256 bits. |rsa_size| must be + // one of 256 or 512 bytes. The vector will be modified in place and will + // result in having a length of 2048 or 4096 bits, depending on the rsa size. + // Returns true on success, false otherwise. + static bool PadRSASHA256Hash(brillo::Blob* hash, size_t rsa_size); + + // Parses the input as a PEM encoded public string. And creates a + // PayloadVerifier with that public key for signature verification. + static std::unique_ptr<PayloadVerifier> CreateInstance( + const std::string& pem_public_key); + + // Extracts the public keys from the certificates contained in the input + // zip file. And creates a PayloadVerifier with these public keys. + static std::unique_ptr<PayloadVerifier> CreateInstanceFromZipPath( + const std::string& certificate_zip_path); + + // Interprets |signature_proto| as a protocol buffer containing the + // |Signatures| message and decrypts each signature data using the stored + // public key. Pads the 32 bytes |sha256_hash_data| to 256 or 512 bytes + // according to the PKCS#1 v1.5 standard; and returns whether *any* of the + // decrypted hashes matches the padded hash data. In case of any error parsing + // the signatures, returns false. + bool VerifySignature(const std::string& signature_proto, + const brillo::Blob& sha256_hash_data) const; + + // Verifies if |sig_data| is a raw signature of the hash |sha256_hash_data|. + // If PayloadVerifier is using RSA as the public key, further puts the + // decrypted data of |sig_data| into |decrypted_sig_data|. + bool VerifyRawSignature(const brillo::Blob& sig_data, + const brillo::Blob& sha256_hash_data, + brillo::Blob* decrypted_sig_data) const; private: - // This should never be constructed - DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadVerifier); + explicit PayloadVerifier( + std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>&& + public_keys) + : public_keys_(std::move(public_keys)) {} + + // Decrypts |sig_data| with the given |public_key| and populates + // |out_hash_data| with the decoded raw hash. Returns true if successful, + // false otherwise. + bool GetRawHashFromSignature(const brillo::Blob& sig_data, + const EVP_PKEY* public_key, + brillo::Blob* out_hash_data) const; + + std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>> public_keys_; }; } // namespace chromeos_update_engine diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc index a0b67eac..e8fa81bc 100644 --- a/payload_consumer/postinstall_runner_action.cc +++ b/payload_consumer/postinstall_runner_action.cc @@ -50,6 +50,7 @@ const int kPostinstallStatusFd = 3; namespace chromeos_update_engine { +using brillo::MessageLoop; using std::string; using std::vector; @@ -75,10 +76,17 @@ void PostinstallRunnerAction::PerformAction() { partition_weight_.resize(install_plan_.partitions.size()); total_weight_ = 0; for (size_t i = 0; i < install_plan_.partitions.size(); ++i) { + auto& partition = install_plan_.partitions[i]; + if (!install_plan_.run_post_install && partition.postinstall_optional) { + partition.run_postinstall = false; + LOG(INFO) << "Skipping optional post-install for partition " + << partition.name << " according to install plan."; + } + // TODO(deymo): This code sets the weight to all the postinstall commands, // but we could remember how long they took in the past and use those // values. - partition_weight_[i] = install_plan_.partitions[i].run_postinstall; + partition_weight_[i] = partition.run_postinstall; total_weight_ += partition_weight_[i]; } accumulated_weight_ = 0; @@ -88,11 +96,6 @@ void PostinstallRunnerAction::PerformAction() { } void PostinstallRunnerAction::PerformPartitionPostinstall() { - if (!install_plan_.run_post_install) { - LOG(INFO) << "Skipping post-install according to install plan."; - return CompletePostinstall(ErrorCode::kSuccess); - } - if (install_plan_.download_url.empty()) { LOG(INFO) << "Skipping post-install during rollback"; return CompletePostinstall(ErrorCode::kSuccess); @@ -290,6 +293,7 @@ void PostinstallRunnerAction::Cleanup() { progress_fd_ = -1; progress_controller_.reset(); + progress_buffer_.clear(); } @@ -336,8 +340,13 @@ void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) { // steps succeeded. if (error_code == ErrorCode::kSuccess) { if (install_plan_.switch_slot_on_reboot) { - if (!boot_control_->SetActiveBootSlot(install_plan_.target_slot)) { + if (!boot_control_->GetDynamicPartitionControl()->FinishUpdate( + install_plan_.powerwash_required) || + !boot_control_->SetActiveBootSlot(install_plan_.target_slot)) { error_code = ErrorCode::kPostinstallRunnerError; + } else { + // Schedules warm reset on next reboot, ignores the error. + hardware_->SetWarmReset(true); } } else { error_code = ErrorCode::kUpdatedButNotActive; diff --git a/payload_consumer/postinstall_runner_action.h b/payload_consumer/postinstall_runner_action.h index 838b2355..e4041079 100644 --- a/payload_consumer/postinstall_runner_action.h +++ b/payload_consumer/postinstall_runner_action.h @@ -97,8 +97,6 @@ class PostinstallRunnerAction : public InstallPlanAction { // ready. Called when the post-install script was run for all the partitions. void CompletePostinstall(ErrorCode error_code); - InstallPlan install_plan_; - // The path where the filesystem will be mounted during post-install. std::string fs_mount_dir_; @@ -141,6 +139,7 @@ class PostinstallRunnerAction : public InstallPlanAction { // The parent progress file descriptor used to watch for progress reports from // the postinstall program and the task watching for them. int progress_fd_{-1}; + std::unique_ptr<base::FileDescriptorWatcher::Controller> progress_controller_; // A buffer of a partial read line from the progress file descriptor. diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc index 84f2c2c4..cf5158b2 100644 --- a/payload_consumer/postinstall_runner_action_unittest.cc +++ b/payload_consumer/postinstall_runner_action_unittest.cc @@ -103,6 +103,8 @@ class PostinstallRunnerActionTest : public ::testing::Test { bool is_rollback, bool save_rollback_data); + void RunPostinstallActionWithInstallPlan(const InstallPlan& install_plan); + public: void ResumeRunningAction() { ASSERT_NE(nullptr, postinstall_action_); @@ -180,9 +182,6 @@ void PostinstallRunnerActionTest::RunPostinstallAction( bool powerwash_required, bool is_rollback, bool save_rollback_data) { - ActionProcessor processor; - processor_ = &processor; - auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>(); InstallPlan::Partition part; part.name = "part"; part.target_path = device_path; @@ -194,6 +193,14 @@ void PostinstallRunnerActionTest::RunPostinstallAction( install_plan.powerwash_required = powerwash_required; install_plan.is_rollback = is_rollback; install_plan.rollback_data_save_requested = save_rollback_data; + RunPostinstallActionWithInstallPlan(install_plan); +} + +void PostinstallRunnerActionTest::RunPostinstallActionWithInstallPlan( + const chromeos_update_engine::InstallPlan& install_plan) { + ActionProcessor processor; + processor_ = &processor; + auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>(); feeder_action->set_obj(install_plan); auto runner_action = std::make_unique<PostinstallRunnerAction>( &fake_boot_control_, &fake_hardware_); @@ -220,7 +227,7 @@ void PostinstallRunnerActionTest::RunPostinstallAction( EXPECT_TRUE(processor_delegate_.processing_stopped_called_ || processor_delegate_.processing_done_called_); if (processor_delegate_.processing_done_called_) { - // Sanity check that the code was set when the processor finishes. + // Validation check that the code was set when the processor finishes. EXPECT_TRUE(processor_delegate_.code_set_); } } @@ -335,6 +342,27 @@ TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) { EXPECT_FALSE(fake_hardware_.GetIsRollbackPowerwashScheduled()); } +TEST_F(PostinstallRunnerActionTest, RunAsRootSkipOptionalPostinstallTest) { + InstallPlan::Partition part; + part.name = "part"; + part.target_path = "/dev/null"; + part.run_postinstall = true; + part.postinstall_path = kPostinstallDefaultScript; + part.postinstall_optional = true; + InstallPlan install_plan; + install_plan.partitions = {part}; + install_plan.download_url = "http://127.0.0.1:8080/update"; + + // Optional postinstalls will be skipped, and the postinstall action succeeds. + RunPostinstallActionWithInstallPlan(install_plan); + EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); + + part.postinstall_optional = false; + install_plan.partitions = {part}; + RunPostinstallActionWithInstallPlan(install_plan); + EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_); +} + // Check that the failures from the postinstall script cause the action to // fail. TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) { diff --git a/payload_consumer/verity_writer_android.cc b/payload_consumer/verity_writer_android.cc index 06d1489b..d5437b64 100644 --- a/payload_consumer/verity_writer_android.cc +++ b/payload_consumer/verity_writer_android.cc @@ -41,6 +41,9 @@ std::unique_ptr<VerityWriterInterface> CreateVerityWriter() { bool VerityWriterAndroid::Init(const InstallPlan::Partition& partition) { partition_ = &partition; + if (partition_->hash_tree_size != 0 || partition_->fec_size != 0) { + utils::SetBlockDeviceReadOnly(partition_->target_path, false); + } if (partition_->hash_tree_size != 0) { auto hash_function = HashTreeBuilder::HashFunction(partition_->hash_tree_algorithm); diff --git a/payload_generator/ab_generator.cc b/payload_generator/ab_generator.cc index f4cc9fba..d9b9d885 100644 --- a/payload_generator/ab_generator.cc +++ b/payload_generator/ab_generator.cc @@ -276,7 +276,7 @@ bool ABGenerator::AddDataAndSetType(AnnotatedOperation* aop, target_part_path, dst_extents, &data, data.size(), kBlockSize)); brillo::Blob blob; - InstallOperation_Type op_type; + InstallOperation::Type op_type; TEST_AND_RETURN_FALSE( diff_utils::GenerateBestFullOperation(data, version, &blob, &op_type)); diff --git a/payload_generator/ab_generator_unittest.cc b/payload_generator/ab_generator_unittest.cc index 170e0e3b..7a952841 100644 --- a/payload_generator/ab_generator_unittest.cc +++ b/payload_generator/ab_generator_unittest.cc @@ -49,7 +49,7 @@ bool ExtentEquals(const Extent& ext, } // Tests splitting of a REPLACE/REPLACE_XZ operation. -void TestSplitReplaceOrReplaceXzOperation(InstallOperation_Type orig_type, +void TestSplitReplaceOrReplaceXzOperation(InstallOperation::Type orig_type, bool compressible) { const size_t op_ex1_start_block = 2; const size_t op_ex1_num_blocks = 2; @@ -124,7 +124,7 @@ void TestSplitReplaceOrReplaceXzOperation(InstallOperation_Type orig_type, version, aop, part_file.path(), &result_ops, &blob_file)); // Check the result. - InstallOperation_Type expected_type = + InstallOperation::Type expected_type = compressible ? InstallOperation::REPLACE_XZ : InstallOperation::REPLACE; ASSERT_EQ(2U, result_ops.size()); @@ -200,7 +200,7 @@ void TestSplitReplaceOrReplaceXzOperation(InstallOperation_Type orig_type, } // Tests merging of REPLACE/REPLACE_XZ operations. -void TestMergeReplaceOrReplaceXzOperations(InstallOperation_Type orig_type, +void TestMergeReplaceOrReplaceXzOperations(InstallOperation::Type orig_type, bool compressible) { const size_t first_op_num_blocks = 1; const size_t second_op_num_blocks = 2; @@ -287,7 +287,7 @@ void TestMergeReplaceOrReplaceXzOperations(InstallOperation_Type orig_type, &aops, version, 5, part_file.path(), &blob_file)); // Check the result. - InstallOperation_Type expected_op_type = + InstallOperation::Type expected_op_type = compressible ? InstallOperation::REPLACE_XZ : InstallOperation::REPLACE; EXPECT_EQ(1U, aops.size()); InstallOperation new_op = aops[0].op; diff --git a/payload_generator/blob_file_writer.cc b/payload_generator/blob_file_writer.cc index 7cdeb352..a1afe873 100644 --- a/payload_generator/blob_file_writer.cc +++ b/payload_generator/blob_file_writer.cc @@ -38,9 +38,9 @@ off_t BlobFileWriter::StoreBlob(const brillo::Blob& blob) { return result; } -void BlobFileWriter::SetTotalBlobs(size_t total_blobs) { - total_blobs_ = total_blobs; - stored_blobs_ = 0; +void BlobFileWriter::IncTotalBlobs(size_t increment) { + base::AutoLock auto_lock(blob_mutex_); + total_blobs_ += increment; } } // namespace chromeos_update_engine diff --git a/payload_generator/blob_file_writer.h b/payload_generator/blob_file_writer.h index 48553be1..bdd4c08c 100644 --- a/payload_generator/blob_file_writer.h +++ b/payload_generator/blob_file_writer.h @@ -35,10 +35,8 @@ class BlobFileWriter { // was stored, or -1 in case of failure. off_t StoreBlob(const brillo::Blob& blob); - // The number of |total_blobs| is the number of blobs that will be stored but - // is only used for logging purposes. If not set or set to 0, logging will be - // skipped. This function will also reset the number of stored blobs to 0. - void SetTotalBlobs(size_t total_blobs); + // Increase |total_blobs| by |increment|. Thread safe. + void IncTotalBlobs(size_t increment); private: size_t total_blobs_{0}; diff --git a/payload_generator/boot_img_filesystem.cc b/payload_generator/boot_img_filesystem.cc index 19de4106..89b175ec 100644 --- a/payload_generator/boot_img_filesystem.cc +++ b/payload_generator/boot_img_filesystem.cc @@ -17,6 +17,7 @@ #include "update_engine/payload_generator/boot_img_filesystem.h" #include <base/logging.h> +#include <bootimg.h> #include <brillo/secure_blob.h> #include <puffin/utils.h> @@ -35,16 +36,61 @@ unique_ptr<BootImgFilesystem> BootImgFilesystem::CreateFromFile( if (filename.empty()) return nullptr; - brillo::Blob header; - if (!utils::ReadFileChunk(filename, 0, sizeof(boot_img_hdr), &header) || - header.size() != sizeof(boot_img_hdr) || - memcmp(header.data(), BOOT_MAGIC, BOOT_MAGIC_SIZE) != 0) { + if (brillo::Blob header_magic; + !utils::ReadFileChunk(filename, 0, BOOT_MAGIC_SIZE, &header_magic) || + memcmp(header_magic.data(), BOOT_MAGIC, BOOT_MAGIC_SIZE) != 0) { + return nullptr; + } + + // The order of image header fields are different in version 3 from the + // previous versions. But the position of "header_version" is fixed at #9 + // across all image headers. + // See details in system/tools/mkbootimg/include/bootimg/bootimg.h + constexpr size_t header_version_offset = + BOOT_MAGIC_SIZE + 8 * sizeof(uint32_t); + brillo::Blob header_version_blob; + if (!utils::ReadFileChunk(filename, + header_version_offset, + sizeof(uint32_t), + &header_version_blob)) { + return nullptr; + } + uint32_t header_version = + *reinterpret_cast<uint32_t*>(header_version_blob.data()); + if (header_version > 3) { + LOG(WARNING) << "Boot image header version " << header_version + << " isn't supported for parsing"; + return nullptr; + } + + // Read the bytes of boot image header based on the header version. + size_t header_size = + header_version == 3 ? sizeof(boot_img_hdr_v3) : sizeof(boot_img_hdr_v0); + brillo::Blob header_blob; + if (!utils::ReadFileChunk(filename, 0, header_size, &header_blob)) { return nullptr; } unique_ptr<BootImgFilesystem> result(new BootImgFilesystem()); result->filename_ = filename; - memcpy(&result->hdr_, header.data(), header.size()); + if (header_version < 3) { + auto hdr_v0 = reinterpret_cast<boot_img_hdr_v0*>(header_blob.data()); + CHECK_EQ(0, memcmp(hdr_v0->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)); + CHECK_LT(hdr_v0->header_version, 3u); + result->kernel_size_ = hdr_v0->kernel_size; + result->ramdisk_size_ = hdr_v0->ramdisk_size; + result->page_size_ = hdr_v0->page_size; + } else { + auto hdr_v3 = reinterpret_cast<boot_img_hdr_v3*>(header_blob.data()); + CHECK_EQ(0, memcmp(hdr_v3->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)); + CHECK_EQ(3u, hdr_v3->header_version); + result->kernel_size_ = hdr_v3->kernel_size; + result->ramdisk_size_ = hdr_v3->ramdisk_size; + result->page_size_ = 4096; + } + + CHECK_GT(result->page_size_, 0u); + return result; } @@ -87,13 +133,13 @@ bool BootImgFilesystem::GetFiles(vector<File>* files) const { files->clear(); const uint64_t file_size = utils::FileSize(filename_); // The first page is header. - uint64_t offset = hdr_.page_size; - if (hdr_.kernel_size > 0 && offset + hdr_.kernel_size <= file_size) { - files->emplace_back(GetFile("<kernel>", offset, hdr_.kernel_size)); + uint64_t offset = page_size_; + if (kernel_size_ > 0 && offset + kernel_size_ <= file_size) { + files->emplace_back(GetFile("<kernel>", offset, kernel_size_)); } - offset += utils::RoundUp(hdr_.kernel_size, hdr_.page_size); - if (hdr_.ramdisk_size > 0 && offset + hdr_.ramdisk_size <= file_size) { - files->emplace_back(GetFile("<ramdisk>", offset, hdr_.ramdisk_size)); + offset += utils::RoundUp(kernel_size_, page_size_); + if (ramdisk_size_ > 0 && offset + ramdisk_size_ <= file_size) { + files->emplace_back(GetFile("<ramdisk>", offset, ramdisk_size_)); } return true; } diff --git a/payload_generator/boot_img_filesystem.h b/payload_generator/boot_img_filesystem.h index 87725d42..61f755c4 100644 --- a/payload_generator/boot_img_filesystem.h +++ b/payload_generator/boot_img_filesystem.h @@ -52,23 +52,9 @@ class BootImgFilesystem : public FilesystemInterface { // The boot.img file path. std::string filename_; -// https://android.googlesource.com/platform/system/core/+/master/mkbootimg/include/bootimg/bootimg.h -#define BOOT_MAGIC "ANDROID!" -#define BOOT_MAGIC_SIZE 8 - struct boot_img_hdr { - // Must be BOOT_MAGIC. - uint8_t magic[BOOT_MAGIC_SIZE]; - uint32_t kernel_size; /* size in bytes */ - uint32_t kernel_addr; /* physical load addr */ - uint32_t ramdisk_size; /* size in bytes */ - uint32_t ramdisk_addr; /* physical load addr */ - uint32_t second_size; /* size in bytes */ - uint32_t second_addr; /* physical load addr */ - uint32_t tags_addr; /* physical addr for kernel tags */ - uint32_t page_size; /* flash page size we assume */ - } __attribute__((packed)); - // The boot image header. - boot_img_hdr hdr_; + uint32_t kernel_size_; /* size in bytes */ + uint32_t ramdisk_size_; /* size in bytes */ + uint32_t page_size_; /* flash page size we assume */ DISALLOW_COPY_AND_ASSIGN(BootImgFilesystem); }; diff --git a/payload_generator/boot_img_filesystem_stub.cc b/payload_generator/boot_img_filesystem_stub.cc new file mode 100644 index 00000000..4928fa1f --- /dev/null +++ b/payload_generator/boot_img_filesystem_stub.cc @@ -0,0 +1,48 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_generator/boot_img_filesystem.h" + +namespace chromeos_update_engine { +std::unique_ptr<BootImgFilesystem> BootImgFilesystem::CreateFromFile( + const std::string& /* filename */) { + return nullptr; +} + +size_t BootImgFilesystem::GetBlockSize() const { + return 4096; +} + +size_t BootImgFilesystem::GetBlockCount() const { + return 0; +} + +FilesystemInterface::File BootImgFilesystem::GetFile( + const std::string& /* name */, + uint64_t /* offset */, + uint64_t /* size */) const { + return {}; +} + +bool BootImgFilesystem::GetFiles(std::vector<File>* /* files */) const { + return false; +} + +bool BootImgFilesystem::LoadSettings(brillo::KeyValueStore* /* store */) const { + return false; +} + +} // namespace chromeos_update_engine diff --git a/payload_generator/boot_img_filesystem_unittest.cc b/payload_generator/boot_img_filesystem_unittest.cc index b1e0d992..0b115e02 100644 --- a/payload_generator/boot_img_filesystem_unittest.cc +++ b/payload_generator/boot_img_filesystem_unittest.cc @@ -18,6 +18,7 @@ #include <vector> +#include <bootimg.h> #include <brillo/secure_blob.h> #include <gtest/gtest.h> @@ -32,18 +33,32 @@ using std::vector; class BootImgFilesystemTest : public ::testing::Test { protected: brillo::Blob GetBootImg(const brillo::Blob& kernel, - const brillo::Blob& ramdisk) { + const brillo::Blob& ramdisk, + bool header_version3 = false) { brillo::Blob boot_img(16 * 1024); - BootImgFilesystem::boot_img_hdr hdr; - memcpy(hdr.magic, BOOT_MAGIC, BOOT_MAGIC_SIZE); - hdr.kernel_size = kernel.size(); - hdr.ramdisk_size = ramdisk.size(); - hdr.page_size = 4096; + constexpr uint32_t page_size = 4096; + size_t offset = 0; - memcpy(boot_img.data() + offset, &hdr, sizeof(hdr)); - offset += utils::RoundUp(sizeof(hdr), hdr.page_size); + if (header_version3) { + boot_img_hdr_v3 hdr_v3{}; + memcpy(hdr_v3.magic, BOOT_MAGIC, BOOT_MAGIC_SIZE); + hdr_v3.kernel_size = kernel.size(); + hdr_v3.ramdisk_size = ramdisk.size(); + hdr_v3.header_version = 3; + memcpy(boot_img.data() + offset, &hdr_v3, sizeof(hdr_v3)); + offset += utils::RoundUp(sizeof(hdr_v3), page_size); + } else { + boot_img_hdr_v0 hdr_v0{}; + memcpy(hdr_v0.magic, BOOT_MAGIC, BOOT_MAGIC_SIZE); + hdr_v0.kernel_size = kernel.size(); + hdr_v0.ramdisk_size = ramdisk.size(); + hdr_v0.page_size = page_size; + hdr_v0.header_version = 0; + memcpy(boot_img.data() + offset, &hdr_v0, sizeof(hdr_v0)); + offset += utils::RoundUp(sizeof(hdr_v0), page_size); + } memcpy(boot_img.data() + offset, kernel.data(), kernel.size()); - offset += utils::RoundUp(kernel.size(), hdr.page_size); + offset += utils::RoundUp(kernel.size(), page_size); memcpy(boot_img.data() + offset, ramdisk.data(), ramdisk.size()); return boot_img; } @@ -76,6 +91,31 @@ TEST_F(BootImgFilesystemTest, SimpleTest) { EXPECT_TRUE(files[1].deflates.empty()); } +TEST_F(BootImgFilesystemTest, ImageHeaderVersion3) { + test_utils::WriteFileVector( + boot_file_.path(), + GetBootImg(brillo::Blob(1000, 'k'), brillo::Blob(5000, 'r'), true)); + unique_ptr<BootImgFilesystem> fs = + BootImgFilesystem::CreateFromFile(boot_file_.path()); + EXPECT_NE(nullptr, fs); + + vector<FilesystemInterface::File> files; + EXPECT_TRUE(fs->GetFiles(&files)); + ASSERT_EQ(2u, files.size()); + + EXPECT_EQ("<kernel>", files[0].name); + EXPECT_EQ(1u, files[0].extents.size()); + EXPECT_EQ(1u, files[0].extents[0].start_block()); + EXPECT_EQ(1u, files[0].extents[0].num_blocks()); + EXPECT_TRUE(files[0].deflates.empty()); + + EXPECT_EQ("<ramdisk>", files[1].name); + EXPECT_EQ(1u, files[1].extents.size()); + EXPECT_EQ(2u, files[1].extents[0].start_block()); + EXPECT_EQ(2u, files[1].extents[0].num_blocks()); + EXPECT_TRUE(files[1].deflates.empty()); +} + TEST_F(BootImgFilesystemTest, BadImageTest) { brillo::Blob boot_img = GetBootImg({}, {}); boot_img[7] = '?'; diff --git a/payload_generator/deflate_utils.cc b/payload_generator/deflate_utils.cc index 5d7a766a..c874bfd2 100644 --- a/payload_generator/deflate_utils.cc +++ b/payload_generator/deflate_utils.cc @@ -74,6 +74,15 @@ bool IsSquashfsImage(const string& part_path, return false; } +bool IsRegularFile(const FilesystemInterface::File& file) { + // If inode is 0, then stat information is invalid for some psuedo files + if (file.file_stat.st_ino != 0 && + (file.file_stat.st_mode & S_IFMT) == S_IFREG) { + return true; + } + return false; +} + // Realigns subfiles |files| of a splitted file |file| into its correct // positions. This can be used for squashfs, zip, apk, etc. bool RealignSplittedFiles(const FilesystemInterface::File& file, @@ -265,7 +274,9 @@ bool PreprocessPartitionFiles(const PartitionConfig& part, result_files->reserve(tmp_files.size()); for (auto& file : tmp_files) { - if (IsSquashfsImage(part.path, file)) { + auto is_regular_file = IsRegularFile(file); + + if (is_regular_file && IsSquashfsImage(part.path, file)) { // Read the image into a file. base::FilePath path; TEST_AND_RETURN_FALSE(base::CreateTemporaryFile(&path)); @@ -296,7 +307,7 @@ bool PreprocessPartitionFiles(const PartitionConfig& part, } } - if (extract_deflates && !file.is_compressed) { + if (is_regular_file && extract_deflates && !file.is_compressed) { // Search for deflates if the file is in zip or gzip format. // .zvoice files may eventually move out of rootfs. If that happens, // remove ".zvoice" (crbug.com/782918). diff --git a/payload_generator/delta_diff_generator.cc b/payload_generator/delta_diff_generator.cc index 595a41ec..c2b35ee8 100644 --- a/payload_generator/delta_diff_generator.cc +++ b/payload_generator/delta_diff_generator.cc @@ -29,14 +29,17 @@ #include <vector> #include <base/logging.h> +#include <base/threading/simple_thread.h> #include "update_engine/common/utils.h" #include "update_engine/payload_consumer/delta_performer.h" #include "update_engine/payload_consumer/payload_constants.h" #include "update_engine/payload_generator/ab_generator.h" +#include "update_engine/payload_generator/annotated_operation.h" #include "update_engine/payload_generator/blob_file_writer.h" #include "update_engine/payload_generator/delta_diff_utils.h" #include "update_engine/payload_generator/full_update_generator.h" +#include "update_engine/payload_generator/merge_sequence_generator.h" #include "update_engine/payload_generator/payload_file.h" using std::string; @@ -49,6 +52,59 @@ namespace chromeos_update_engine { const size_t kRootFSPartitionSize = static_cast<size_t>(2) * 1024 * 1024 * 1024; const size_t kBlockSize = 4096; // bytes +class PartitionProcessor : public base::DelegateSimpleThread::Delegate { + public: + explicit PartitionProcessor( + const PayloadGenerationConfig& config, + const PartitionConfig& old_part, + const PartitionConfig& new_part, + BlobFileWriter* file_writer, + std::vector<AnnotatedOperation>* aops, + std::vector<CowMergeOperation>* cow_merge_sequence, + std::unique_ptr<chromeos_update_engine::OperationsGenerator> strategy) + : config_(config), + old_part_(old_part), + new_part_(new_part), + file_writer_(file_writer), + aops_(aops), + cow_merge_sequence_(cow_merge_sequence), + strategy_(std::move(strategy)) {} + PartitionProcessor(PartitionProcessor&&) noexcept = default; + void Run() override { + LOG(INFO) << "Started an async task to process partition " + << old_part_.name; + bool success = strategy_->GenerateOperations( + config_, old_part_, new_part_, file_writer_, aops_); + if (!success) { + // ABORT the entire process, so that developer can look + // at recent logs and diagnose what happened + LOG(FATAL) << "GenerateOperations(" << old_part_.name << ", " + << new_part_.name << ") failed"; + } + + bool snapshot_enabled = + config_.target.dynamic_partition_metadata && + config_.target.dynamic_partition_metadata->snapshot_enabled(); + if (old_part_.path.empty() || !snapshot_enabled) { + return; + } + auto generator = MergeSequenceGenerator::Create(*aops_); + if (!generator || !generator->Generate(cow_merge_sequence_)) { + LOG(FATAL) << "Failed to generate merge sequence"; + } + } + + private: + const PayloadGenerationConfig& config_; + const PartitionConfig& old_part_; + const PartitionConfig& new_part_; + BlobFileWriter* file_writer_; + std::vector<AnnotatedOperation>* aops_; + std::vector<CowMergeOperation>* cow_merge_sequence_; + std::unique_ptr<chromeos_update_engine::OperationsGenerator> strategy_; + DISALLOW_COPY_AND_ASSIGN(PartitionProcessor); +}; + bool GenerateUpdatePayloadFile(const PayloadGenerationConfig& config, const string& output_path, const string& private_key_path, @@ -80,6 +136,15 @@ bool GenerateUpdatePayloadFile(const PayloadGenerationConfig& config, config.target.partitions.size()); } PartitionConfig empty_part(""); + std::vector<std::vector<AnnotatedOperation>> all_aops; + all_aops.resize(config.target.partitions.size()); + std::vector<std::vector<CowMergeOperation>> all_merge_sequences; + all_merge_sequences.resize(config.target.partitions.size()); + std::vector<PartitionProcessor> partition_tasks{}; + auto thread_count = std::min<int>(diff_utils::GetMaxThreads(), + config.target.partitions.size()); + base::DelegateSimpleThreadPool thread_pool{"partition-thread-pool", + thread_count}; for (size_t i = 0; i < config.target.partitions.size(); i++) { const PartitionConfig& old_part = config.is_delta ? config.source.partitions[i] : empty_part; @@ -99,12 +164,30 @@ bool GenerateUpdatePayloadFile(const PayloadGenerationConfig& config, strategy.reset(new FullUpdateGenerator()); } - vector<AnnotatedOperation> aops; // Generate the operations using the strategy we selected above. - TEST_AND_RETURN_FALSE(strategy->GenerateOperations( - config, old_part, new_part, &blob_file, &aops)); + partition_tasks.push_back(PartitionProcessor(config, + old_part, + new_part, + &blob_file, + &all_aops[i], + &all_merge_sequences[i], + std::move(strategy))); + } + thread_pool.Start(); + for (auto& processor : partition_tasks) { + thread_pool.AddWork(&processor); + } + thread_pool.JoinAll(); - TEST_AND_RETURN_FALSE(payload.AddPartition(old_part, new_part, aops)); + for (size_t i = 0; i < config.target.partitions.size(); i++) { + const PartitionConfig& old_part = + config.is_delta ? config.source.partitions[i] : empty_part; + const PartitionConfig& new_part = config.target.partitions[i]; + TEST_AND_RETURN_FALSE( + payload.AddPartition(old_part, + new_part, + std::move(all_aops[i]), + std::move(all_merge_sequences[i]))); } } diff --git a/payload_generator/delta_diff_utils.cc b/payload_generator/delta_diff_utils.cc index ded30fb6..220c7ae1 100644 --- a/payload_generator/delta_diff_utils.cc +++ b/payload_generator/delta_diff_utils.cc @@ -637,7 +637,7 @@ bool DeltaReadFile(vector<AnnotatedOperation>* aops, bool GenerateBestFullOperation(const brillo::Blob& new_data, const PayloadVersion& version, brillo::Blob* out_blob, - InstallOperation_Type* out_type) { + InstallOperation::Type* out_type) { if (new_data.empty()) return false; @@ -739,7 +739,7 @@ bool ReadExtentsToDiff(const string& old_part, // Try generating a full operation for the given new data, regardless of the // old_data. - InstallOperation_Type op_type; + InstallOperation::Type op_type; TEST_AND_RETURN_FALSE( GenerateBestFullOperation(new_data, version, &data_blob, &op_type)); operation.set_type(op_type); @@ -766,7 +766,7 @@ bool ReadExtentsToDiff(const string& old_part, ScopedPathUnlinker unlinker(patch.value()); std::unique_ptr<bsdiff::PatchWriterInterface> bsdiff_patch_writer; - InstallOperation_Type operation_type = InstallOperation::SOURCE_BSDIFF; + InstallOperation::Type operation_type = InstallOperation::SOURCE_BSDIFF; if (version.OperationAllowed(InstallOperation::BROTLI_BSDIFF)) { bsdiff_patch_writer = bsdiff::CreateBSDF2PatchWriter(patch.value(), @@ -872,13 +872,13 @@ bool ReadExtentsToDiff(const string& old_part, return true; } -bool IsAReplaceOperation(InstallOperation_Type op_type) { +bool IsAReplaceOperation(InstallOperation::Type op_type) { return (op_type == InstallOperation::REPLACE || op_type == InstallOperation::REPLACE_BZ || op_type == InstallOperation::REPLACE_XZ); } -bool IsNoSourceOperation(InstallOperation_Type op_type) { +bool IsNoSourceOperation(InstallOperation::Type op_type) { return (IsAReplaceOperation(op_type) || op_type == InstallOperation::ZERO || op_type == InstallOperation::DISCARD); } @@ -938,7 +938,7 @@ bool IsExtFilesystem(const string& device) { if (magic != EXT2_SUPER_MAGIC) return false; - // Sanity check the parameters. + // Validation check the parameters. TEST_AND_RETURN_FALSE(log_block_size >= EXT2_MIN_BLOCK_LOG_SIZE && log_block_size <= EXT2_MAX_BLOCK_LOG_SIZE); TEST_AND_RETURN_FALSE(block_count > 0); diff --git a/payload_generator/delta_diff_utils.h b/payload_generator/delta_diff_utils.h index a062327d..c75d16d9 100644 --- a/payload_generator/delta_diff_utils.h +++ b/payload_generator/delta_diff_utils.h @@ -119,13 +119,13 @@ bool ReadExtentsToDiff(const std::string& old_part, bool GenerateBestFullOperation(const brillo::Blob& new_data, const PayloadVersion& version, brillo::Blob* out_blob, - InstallOperation_Type* out_type); + InstallOperation::Type* out_type); // Returns whether |op_type| is one of the REPLACE full operations. -bool IsAReplaceOperation(InstallOperation_Type op_type); +bool IsAReplaceOperation(InstallOperation::Type op_type); // Returns true if an operation with type |op_type| has no |src_extents|. -bool IsNoSourceOperation(InstallOperation_Type op_type); +bool IsNoSourceOperation(InstallOperation::Type op_type); bool InitializePartitionInfo(const PartitionConfig& partition, PartitionInfo* info); diff --git a/payload_generator/delta_diff_utils_unittest.cc b/payload_generator/delta_diff_utils_unittest.cc index 8a97b1bf..0857f9c1 100644 --- a/payload_generator/delta_diff_utils_unittest.cc +++ b/payload_generator/delta_diff_utils_unittest.cc @@ -231,7 +231,7 @@ TEST_F(DeltaDiffUtilsTest, ReplaceSmallTest) { EXPECT_FALSE(data.empty()); EXPECT_TRUE(op.has_type()); - const InstallOperation_Type expected_type = + const InstallOperation::Type expected_type = (i == 0 ? InstallOperation::REPLACE : InstallOperation::REPLACE_BZ); EXPECT_EQ(expected_type, op.type()); EXPECT_FALSE(op.has_data_offset()); diff --git a/payload_generator/extent_ranges.cc b/payload_generator/extent_ranges.cc index 4600efe7..2098639b 100644 --- a/payload_generator/extent_ranges.cc +++ b/payload_generator/extent_ranges.cc @@ -202,6 +202,15 @@ void ExtentRanges::SubtractRepeatedExtents( } } +bool ExtentRanges::OverlapsWithExtent(const Extent& extent) const { + for (const auto& entry : extent_set_) { + if (ExtentsOverlap(entry, extent)) { + return true; + } + } + return false; +} + bool ExtentRanges::ContainsBlock(uint64_t block) const { auto lower = extent_set_.lower_bound(ExtentForRange(block, 1)); // The block could be on the extent before the one in |lower|. diff --git a/payload_generator/extent_ranges.h b/payload_generator/extent_ranges.h index 62ffff40..68aa27f8 100644 --- a/payload_generator/extent_ranges.h +++ b/payload_generator/extent_ranges.h @@ -63,6 +63,9 @@ class ExtentRanges { void AddRanges(const ExtentRanges& ranges); void SubtractRanges(const ExtentRanges& ranges); + // Returns true if the input extent overlaps with the current ExtentRanges. + bool OverlapsWithExtent(const Extent& extent) const; + // Returns whether the block |block| is in this ExtentRange. bool ContainsBlock(uint64_t block) const; diff --git a/payload_generator/full_update_generator.cc b/payload_generator/full_update_generator.cc index 4d8b2f99..4a5f63a9 100644 --- a/payload_generator/full_update_generator.cc +++ b/payload_generator/full_update_generator.cc @@ -99,7 +99,7 @@ bool ChunkProcessor::ProcessChunk() { fd_, buffer_in_.data(), buffer_in_.size(), offset_, &bytes_read)); TEST_AND_RETURN_FALSE(bytes_read == static_cast<ssize_t>(size_)); - InstallOperation_Type op_type; + InstallOperation::Type op_type; TEST_AND_RETURN_FALSE(diff_utils::GenerateBestFullOperation( buffer_in_, version_, &op_blob, &op_type)); @@ -153,7 +153,7 @@ bool FullUpdateGenerator::GenerateOperations( aops->resize(num_chunks); vector<ChunkProcessor> chunk_processors; chunk_processors.reserve(num_chunks); - blob_file->SetTotalBlobs(num_chunks); + blob_file->IncTotalBlobs(num_chunks); for (size_t i = 0; i < num_chunks; ++i) { size_t start_block = i * chunk_blocks; @@ -187,9 +187,6 @@ bool FullUpdateGenerator::GenerateOperations( thread_pool.AddWork(&processor); thread_pool.JoinAll(); - // All the work done, disable logging. - blob_file->SetTotalBlobs(0); - // All the operations must have a type set at this point. Otherwise, a // ChunkProcessor failed to complete. for (const AnnotatedOperation& aop : *aops) { diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc index fe0a10be..5c1fb476 100644 --- a/payload_generator/generate_delta_main.cc +++ b/payload_generator/generate_delta_main.cc @@ -14,6 +14,7 @@ // limitations under the License. // +#include <map> #include <string> #include <vector> @@ -22,6 +23,7 @@ #include <base/logging.h> #include <base/strings/string_number_conversions.h> #include <base/strings/string_split.h> +#include <base/strings/string_util.h> #include <brillo/flag_helper.h> #include <brillo/key_value_store.h> #include <brillo/message_loops/base_message_loop.h> @@ -47,6 +49,7 @@ // and an output file as arguments and the path to an output file and // generates a delta that can be sent to Chrome OS clients. +using std::map; using std::string; using std::vector; @@ -58,18 +61,15 @@ constexpr char kPayloadPropertiesFormatKeyValue[] = "key-value"; constexpr char kPayloadPropertiesFormatJson[] = "json"; void ParseSignatureSizes(const string& signature_sizes_flag, - vector<int>* signature_sizes) { + vector<size_t>* signature_sizes) { signature_sizes->clear(); vector<string> split_strings = base::SplitString( signature_sizes_flag, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); for (const string& str : split_strings) { - int size = 0; - bool parsing_successful = base::StringToInt(str, &size); + size_t size = 0; + bool parsing_successful = base::StringToSizeT(str, &size); LOG_IF(FATAL, !parsing_successful) << "Invalid signature size: " << str; - LOG_IF(FATAL, size != (2048 / 8)) - << "Only signature sizes of 256 bytes are supported."; - signature_sizes->push_back(size); } } @@ -106,7 +106,7 @@ bool ParseImageInfo(const string& channel, return true; } -void CalculateHashForSigning(const vector<int>& sizes, +void CalculateHashForSigning(const vector<size_t>& sizes, const string& out_hash_file, const string& out_metadata_hash_file, const string& in_file) { @@ -142,6 +142,7 @@ void SignatureFileFlagToBlobs(const string& signature_file_flag, void SignPayload(const string& in_file, const string& out_file, + const vector<size_t>& signature_sizes, const string& payload_signature_file, const string& metadata_signature_file, const string& out_metadata_size_file) { @@ -155,6 +156,7 @@ void SignPayload(const string& in_file, SignatureFileFlagToBlobs(metadata_signature_file, &metadata_signatures); uint64_t final_metadata_size; CHECK(PayloadSigner::AddSignatureToPayload(in_file, + signature_sizes, payload_signatures, metadata_signatures, out_file, @@ -253,8 +255,8 @@ bool ApplyPayload(const string& payload_file, nullptr, new FileFetcher(), true /* interactive */); - auto filesystem_verifier_action = - std::make_unique<FilesystemVerifierAction>(); + auto filesystem_verifier_action = std::make_unique<FilesystemVerifierAction>( + fake_boot_control.GetDynamicPartitionControl()); BondActions(install_plan_action.get(), download_action.get()); BondActions(download_action.get(), filesystem_verifier_action.get()); @@ -295,6 +297,39 @@ bool ExtractProperties(const string& payload_path, return true; } +template <typename Key, typename Val> +string ToString(const map<Key, Val>& map) { + vector<string> result; + result.reserve(map.size()); + for (const auto& it : map) { + result.emplace_back(it.first + ": " + it.second); + } + return "{" + base::JoinString(result, ",") + "}"; +} + +bool ParsePerPartitionTimestamps(const string& partition_timestamps, + PayloadGenerationConfig* config) { + base::StringPairs pairs; + CHECK(base::SplitStringIntoKeyValuePairs( + partition_timestamps, ':', ',', &pairs)) + << "--partition_timestamps accepts commad " + "separated pairs. e.x. system:1234,vendor:5678"; + map<string, string> partition_timestamps_map{ + std::move_iterator(pairs.begin()), std::move_iterator(pairs.end())}; + for (auto&& partition : config->target.partitions) { + auto&& it = partition_timestamps_map.find(partition.name); + if (it != partition_timestamps_map.end()) { + partition.version = std::move(it->second); + partition_timestamps_map.erase(it); + } + } + if (!partition_timestamps_map.empty()) { + LOG(ERROR) << "Unused timestamps: " << ToString(partition_timestamps_map); + return false; + } + return true; +} + int Main(int argc, char** argv) { DEFINE_string(old_image, "", "Path to the old rootfs"); DEFINE_string(new_image, "", "Path to the new rootfs"); @@ -385,6 +420,11 @@ int Main(int argc, char** argv) { 0, "The maximum timestamp of the OS allowed to apply this " "payload."); + DEFINE_string( + partition_timestamps, + "", + "The per-partition maximum timestamps which the OS allowed to apply this " + "payload. Passed in comma separated pairs, e.x. system:1234,vendor:5678"); DEFINE_string(old_channel, "", @@ -439,6 +479,17 @@ int Main(int argc, char** argv) { "", "An info file specifying dynamic partition metadata. " "Only allowed in major version 2 or newer."); + DEFINE_bool(disable_fec_computation, + false, + "Disables the fec data computation on device."); + DEFINE_string( + out_maximum_signature_size_file, + "", + "Path to the output maximum signature size given a private key."); + DEFINE_bool(is_partial_update, + false, + "The payload only targets a subset of partitions on the device," + "e.g. generic kernel image update."); brillo::FlagHelper::Init( argc, @@ -464,8 +515,34 @@ int Main(int argc, char** argv) { // Initialize the Xz compressor. XzCompressInit(); - vector<int> signature_sizes; - ParseSignatureSizes(FLAGS_signature_size, &signature_sizes); + if (!FLAGS_out_maximum_signature_size_file.empty()) { + LOG_IF(FATAL, FLAGS_private_key.empty()) + << "Private key is not provided when calculating the maximum signature " + "size."; + + size_t maximum_signature_size; + if (!PayloadSigner::GetMaximumSignatureSize(FLAGS_private_key, + &maximum_signature_size)) { + LOG(ERROR) << "Failed to get the maximum signature size of private key: " + << FLAGS_private_key; + return 1; + } + // Write the size string to output file. + string signature_size_string = std::to_string(maximum_signature_size); + if (!utils::WriteFile(FLAGS_out_maximum_signature_size_file.c_str(), + signature_size_string.c_str(), + signature_size_string.size())) { + PLOG(ERROR) << "Failed to write the maximum signature size to " + << FLAGS_out_maximum_signature_size_file << "."; + return 1; + } + return 0; + } + + vector<size_t> signature_sizes; + if (!FLAGS_signature_size.empty()) { + ParseSignatureSizes(FLAGS_signature_size, &signature_sizes); + } if (!FLAGS_out_hash_file.empty() || !FLAGS_out_metadata_hash_file.empty()) { CHECK(FLAGS_out_metadata_size_file.empty()); @@ -478,6 +555,7 @@ int Main(int argc, char** argv) { if (!FLAGS_payload_signature_file.empty()) { SignPayload(FLAGS_in_file, FLAGS_out_file, + signature_sizes, FLAGS_payload_signature_file, FLAGS_metadata_signature_file, FLAGS_out_metadata_size_file); @@ -546,6 +624,8 @@ int Main(int argc, char** argv) { << "Partition name can't be empty, see --partition_names."; payload_config.target.partitions.emplace_back(partition_names[i]); payload_config.target.partitions.back().path = new_partitions[i]; + payload_config.target.partitions.back().disable_fec_computation = + FLAGS_disable_fec_computation; if (i < new_mapfiles.size()) payload_config.target.partitions.back().mapfile_path = new_mapfiles[i]; } @@ -598,6 +678,10 @@ int Main(int argc, char** argv) { CHECK(payload_config.target.ValidateDynamicPartitionMetadata()); } + if (FLAGS_is_partial_update) { + payload_config.is_partial_update = true; + } + CHECK(!FLAGS_out_file.empty()); // Ignore failures. These are optional arguments. @@ -670,8 +754,13 @@ int Main(int argc, char** argv) { } payload_config.max_timestamp = FLAGS_max_timestamp; + if (!FLAGS_partition_timestamps.empty()) { + CHECK(ParsePerPartitionTimestamps(FLAGS_partition_timestamps, + &payload_config)); + } - if (payload_config.version.minor >= kVerityMinorPayloadVersion) + if (payload_config.is_delta && + payload_config.version.minor >= kVerityMinorPayloadVersion) CHECK(payload_config.target.LoadVerityConfig()); LOG(INFO) << "Generating " << (payload_config.is_delta ? "delta" : "full") diff --git a/payload_generator/merge_sequence_generator.cc b/payload_generator/merge_sequence_generator.cc new file mode 100644 index 00000000..eaffeac2 --- /dev/null +++ b/payload_generator/merge_sequence_generator.cc @@ -0,0 +1,269 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_generator/merge_sequence_generator.h" + +#include <algorithm> + +#include "update_engine/payload_generator/extent_utils.h" + +namespace chromeos_update_engine { + +CowMergeOperation CreateCowMergeOperation(const Extent& src_extent, + const Extent& dst_extent) { + CowMergeOperation ret; + ret.set_type(CowMergeOperation::COW_COPY); + *ret.mutable_src_extent() = src_extent; + *ret.mutable_dst_extent() = dst_extent; + return ret; +} + +std::ostream& operator<<(std::ostream& os, + const CowMergeOperation& merge_operation) { + os << "CowMergeOperation src extent: " + << ExtentsToString({merge_operation.src_extent()}) + << ", dst extent: " << ExtentsToString({merge_operation.dst_extent()}); + return os; +} + +// The OTA generation guarantees that all blocks in the dst extent will be +// written only once. So we can use it to order the CowMergeOperation. +bool operator<(const CowMergeOperation& op1, const CowMergeOperation& op2) { + return op1.dst_extent().start_block() < op2.dst_extent().start_block(); +} + +bool operator==(const CowMergeOperation& op1, const CowMergeOperation& op2) { + return op1.type() == op2.type() && op1.src_extent() == op2.src_extent() && + op1.dst_extent() == op2.dst_extent(); +} + +std::unique_ptr<MergeSequenceGenerator> MergeSequenceGenerator::Create( + const std::vector<AnnotatedOperation>& aops) { + std::vector<CowMergeOperation> sequence; + for (const auto& aop : aops) { + // Only handle SOURCE_COPY now for the cow size optimization. + if (aop.op.type() != InstallOperation::SOURCE_COPY) { + continue; + } + if (aop.op.dst_extents().size() != 1) { + std::vector<Extent> out_extents; + ExtentsToVector(aop.op.dst_extents(), &out_extents); + LOG(ERROR) << "The dst extents for source_copy expects to be contiguous," + << " dst extents: " << ExtentsToString(out_extents); + return nullptr; + } + + // Split the source extents. + size_t used_blocks = 0; + for (const auto& src_extent : aop.op.src_extents()) { + // The dst_extent in the merge sequence will be a subset of + // InstallOperation's dst_extent. This will simplify the OTA -> COW + // conversion when we install the payload. + Extent dst_extent = + ExtentForRange(aop.op.dst_extents(0).start_block() + used_blocks, + src_extent.num_blocks()); + sequence.emplace_back(CreateCowMergeOperation(src_extent, dst_extent)); + used_blocks += src_extent.num_blocks(); + } + + if (used_blocks != aop.op.dst_extents(0).num_blocks()) { + LOG(ERROR) << "Number of blocks in src extents doesn't equal to the" + << " ones in the dst extents, src blocks " << used_blocks + << ", dst blocks " << aop.op.dst_extents(0).num_blocks(); + return nullptr; + } + } + + std::sort(sequence.begin(), sequence.end()); + return std::unique_ptr<MergeSequenceGenerator>( + new MergeSequenceGenerator(sequence)); +} + +bool MergeSequenceGenerator::FindDependency( + std::map<CowMergeOperation, std::set<CowMergeOperation>>* result) const { + CHECK(result); + LOG(INFO) << "Finding dependencies"; + + // Since the OTA operation may reuse some source blocks, use the binary + // search on sorted dst extents to find overlaps. + std::map<CowMergeOperation, std::set<CowMergeOperation>> merge_after; + for (const auto& op : operations_) { + // lower bound (inclusive): dst extent's end block >= src extent's start + // block. + const auto lower_it = std::lower_bound( + operations_.begin(), + operations_.end(), + op, + [](const CowMergeOperation& it, const CowMergeOperation& op) { + auto dst_end_block = + it.dst_extent().start_block() + it.dst_extent().num_blocks() - 1; + return dst_end_block < op.src_extent().start_block(); + }); + // upper bound: dst extent's start block > src extent's end block + const auto upper_it = std::upper_bound( + lower_it, + operations_.end(), + op, + [](const CowMergeOperation& op, const CowMergeOperation& it) { + auto src_end_block = + op.src_extent().start_block() + op.src_extent().num_blocks() - 1; + return src_end_block < it.dst_extent().start_block(); + }); + + // TODO(xunchang) skip inserting the empty set to merge_after. + if (lower_it == upper_it) { + merge_after.insert({op, {}}); + } else { + std::set<CowMergeOperation> operations(lower_it, upper_it); + auto it = operations.find(op); + if (it != operations.end()) { + LOG(INFO) << "Self overlapping " << op; + operations.erase(it); + } + auto ret = merge_after.emplace(op, std::move(operations)); + // Check the insertion indeed happens. + CHECK(ret.second); + } + } + + *result = std::move(merge_after); + return true; +} + +bool MergeSequenceGenerator::Generate( + std::vector<CowMergeOperation>* sequence) const { + sequence->clear(); + std::map<CowMergeOperation, std::set<CowMergeOperation>> merge_after; + if (!FindDependency(&merge_after)) { + LOG(ERROR) << "Failed to find dependencies"; + return false; + } + + LOG(INFO) << "Generating sequence"; + + // Use the non-DFS version of the topology sort. So we can control the + // operations to discard to break cycles; thus yielding a deterministic + // sequence. + std::map<CowMergeOperation, int> incoming_edges; + for (const auto& it : merge_after) { + for (const auto& blocked : it.second) { + // Value is default initialized to 0. + incoming_edges[blocked] += 1; + } + } + + std::set<CowMergeOperation> free_operations; + for (const auto& op : operations_) { + if (incoming_edges.find(op) == incoming_edges.end()) { + free_operations.insert(op); + } + } + + std::vector<CowMergeOperation> merge_sequence; + std::set<CowMergeOperation> convert_to_raw; + while (!incoming_edges.empty()) { + if (!free_operations.empty()) { + merge_sequence.insert( + merge_sequence.end(), free_operations.begin(), free_operations.end()); + } else { + auto to_convert = incoming_edges.begin()->first; + free_operations.insert(to_convert); + convert_to_raw.insert(to_convert); + LOG(INFO) << "Converting operation to raw " << to_convert; + } + + std::set<CowMergeOperation> next_free_operations; + for (const auto& op : free_operations) { + incoming_edges.erase(op); + + // Now that this particular operation is merged, other operations blocked + // by this one may be free. Decrement the count of blocking operations, + // and set up the free operations for the next iteration. + for (const auto& blocked : merge_after[op]) { + auto it = incoming_edges.find(blocked); + if (it == incoming_edges.end()) { + continue; + } + + auto blocking_transfer_count = &it->second; + if (*blocking_transfer_count <= 0) { + LOG(ERROR) << "Unexpected count in merge after map " + << blocking_transfer_count; + return false; + } + // This operation is no longer blocked by anyone. Add it to the merge + // sequence in the next iteration. + *blocking_transfer_count -= 1; + if (*blocking_transfer_count == 0) { + next_free_operations.insert(blocked); + } + } + } + + LOG(INFO) << "Remaining transfers " << incoming_edges.size() + << ", free transfers " << free_operations.size() + << ", merge_sequence size " << merge_sequence.size(); + free_operations = std::move(next_free_operations); + } + + if (!free_operations.empty()) { + merge_sequence.insert( + merge_sequence.end(), free_operations.begin(), free_operations.end()); + } + + CHECK_EQ(operations_.size(), merge_sequence.size() + convert_to_raw.size()); + + size_t blocks_in_sequence = 0; + for (const CowMergeOperation& transfer : merge_sequence) { + blocks_in_sequence += transfer.dst_extent().num_blocks(); + } + + size_t blocks_in_raw = 0; + for (const CowMergeOperation& transfer : convert_to_raw) { + blocks_in_raw += transfer.dst_extent().num_blocks(); + } + + LOG(INFO) << "Blocks in merge sequence " << blocks_in_sequence + << ", blocks in raw " << blocks_in_raw; + if (!ValidateSequence(merge_sequence)) { + return false; + } + + *sequence = std::move(merge_sequence); + return true; +} + +bool MergeSequenceGenerator::ValidateSequence( + const std::vector<CowMergeOperation>& sequence) { + LOG(INFO) << "Validating merge sequence"; + ExtentRanges visited; + for (const auto& op : sequence) { + if (visited.OverlapsWithExtent(op.src_extent())) { + LOG(ERROR) << "Transfer violates the merge sequence " << op + << "Visited extent ranges: "; + visited.Dump(); + return false; + } + + CHECK(!visited.OverlapsWithExtent(op.dst_extent())) + << "dst extent should write only once."; + visited.AddExtent(op.dst_extent()); + } + + return true; +} + +} // namespace chromeos_update_engine diff --git a/payload_generator/merge_sequence_generator.h b/payload_generator/merge_sequence_generator.h new file mode 100644 index 00000000..bc0158ee --- /dev/null +++ b/payload_generator/merge_sequence_generator.h @@ -0,0 +1,74 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_MERGE_SEQUENCE_GENERATOR_H_ +#define UPDATE_ENGINE_PAYLOAD_GENERATOR_MERGE_SEQUENCE_GENERATOR_H_ + +#include <map> +#include <memory> +#include <set> +#include <utility> +#include <vector> + +#include "update_engine/payload_generator/annotated_operation.h" +#include "update_engine/payload_generator/extent_ranges.h" +#include "update_engine/payload_generator/extent_utils.h" +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { +// Constructs CowMergeOperation from src & dst extents +CowMergeOperation CreateCowMergeOperation(const Extent& src_extent, + const Extent& dst_extent); + +// Comparator for CowMergeOperation. +bool operator<(const CowMergeOperation& op1, const CowMergeOperation& op2); +bool operator==(const CowMergeOperation& op1, const CowMergeOperation& op2); + +std::ostream& operator<<(std::ostream& os, + const CowMergeOperation& merge_operation); + +// This class takes a list of CowMergeOperations; and sorts them so that no +// read after write will happen by following the sequence. When there is a +// cycle, we will omit some operations in the list. Therefore, the result +// sequence may not contain all blocks in the input list. +class MergeSequenceGenerator { + public: + // Creates an object from a list of OTA InstallOperations. Returns nullptr on + // failure. + static std::unique_ptr<MergeSequenceGenerator> Create( + const std::vector<AnnotatedOperation>& aops); + // Checks that no read after write happens in the given sequence. + static bool ValidateSequence(const std::vector<CowMergeOperation>& sequence); + + // Generates a merge sequence from |operations_|, puts the result in + // |sequence|. Returns false on failure. + bool Generate(std::vector<CowMergeOperation>* sequence) const; + + private: + friend class MergeSequenceGeneratorTest; + explicit MergeSequenceGenerator(std::vector<CowMergeOperation> transfers) + : operations_(std::move(transfers)) {} + + // For a given merge operation, finds all the operations that should merge + // after myself. Put the result in |merge_after|. + bool FindDependency(std::map<CowMergeOperation, std::set<CowMergeOperation>>* + merge_after) const; + // The list of CowMergeOperations to sort. + std::vector<CowMergeOperation> operations_; +}; + +} // namespace chromeos_update_engine +#endif diff --git a/payload_generator/merge_sequence_generator_unittest.cc b/payload_generator/merge_sequence_generator_unittest.cc new file mode 100644 index 00000000..567ede1e --- /dev/null +++ b/payload_generator/merge_sequence_generator_unittest.cc @@ -0,0 +1,196 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <algorithm> +#include <vector> + +#include <gtest/gtest.h> + +#include "update_engine/payload_consumer/payload_constants.h" +#include "update_engine/payload_generator/extent_utils.h" +#include "update_engine/payload_generator/merge_sequence_generator.h" + +namespace chromeos_update_engine { +class MergeSequenceGeneratorTest : public ::testing::Test { + protected: + void VerifyTransfers(MergeSequenceGenerator* generator, + const std::vector<CowMergeOperation>& expected) { + ASSERT_EQ(expected, generator->operations_); + } + + void FindDependency( + std::vector<CowMergeOperation> transfers, + std::map<CowMergeOperation, std::set<CowMergeOperation>>* result) { + std::sort(transfers.begin(), transfers.end()); + MergeSequenceGenerator generator(std::move(transfers)); + ASSERT_TRUE(generator.FindDependency(result)); + } + + void GenerateSequence(std::vector<CowMergeOperation> transfers, + const std::vector<CowMergeOperation>& expected) { + std::sort(transfers.begin(), transfers.end()); + MergeSequenceGenerator generator(std::move(transfers)); + std::vector<CowMergeOperation> sequence; + ASSERT_TRUE(generator.Generate(&sequence)); + ASSERT_EQ(expected, sequence); + } +}; + +TEST_F(MergeSequenceGeneratorTest, Create) { + std::vector<AnnotatedOperation> aops{{"file1", {}}, {"file2", {}}}; + aops[0].op.set_type(InstallOperation::SOURCE_COPY); + *aops[0].op.add_src_extents() = ExtentForRange(10, 10); + *aops[0].op.add_dst_extents() = ExtentForRange(30, 10); + + aops[1].op.set_type(InstallOperation::SOURCE_COPY); + *aops[1].op.add_src_extents() = ExtentForRange(20, 10); + *aops[1].op.add_dst_extents() = ExtentForRange(40, 10); + + auto generator = MergeSequenceGenerator::Create(aops); + ASSERT_TRUE(generator); + std::vector<CowMergeOperation> expected = { + CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(30, 10)), + CreateCowMergeOperation(ExtentForRange(20, 10), ExtentForRange(40, 10))}; + VerifyTransfers(generator.get(), expected); + + *aops[1].op.add_src_extents() = ExtentForRange(30, 5); + *aops[1].op.add_dst_extents() = ExtentForRange(50, 5); + generator = MergeSequenceGenerator::Create(aops); + ASSERT_FALSE(generator); +} + +TEST_F(MergeSequenceGeneratorTest, Create_SplitSource) { + InstallOperation op; + op.set_type(InstallOperation::SOURCE_COPY); + *(op.add_src_extents()) = ExtentForRange(2, 3); + *(op.add_src_extents()) = ExtentForRange(6, 1); + *(op.add_src_extents()) = ExtentForRange(8, 4); + *(op.add_dst_extents()) = ExtentForRange(10, 8); + + AnnotatedOperation aop{"file1", op}; + auto generator = MergeSequenceGenerator::Create({aop}); + ASSERT_TRUE(generator); + std::vector<CowMergeOperation> expected = { + CreateCowMergeOperation(ExtentForRange(2, 3), ExtentForRange(10, 3)), + CreateCowMergeOperation(ExtentForRange(6, 1), ExtentForRange(13, 1)), + CreateCowMergeOperation(ExtentForRange(8, 4), ExtentForRange(14, 4))}; + VerifyTransfers(generator.get(), expected); +} + +TEST_F(MergeSequenceGeneratorTest, FindDependency) { + std::vector<CowMergeOperation> transfers = { + CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)), + CreateCowMergeOperation(ExtentForRange(40, 10), ExtentForRange(50, 10)), + }; + + std::map<CowMergeOperation, std::set<CowMergeOperation>> merge_after; + FindDependency(transfers, &merge_after); + ASSERT_EQ(std::set<CowMergeOperation>(), merge_after.at(transfers[0])); + ASSERT_EQ(std::set<CowMergeOperation>(), merge_after.at(transfers[1])); + + transfers = { + CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(25, 10)), + CreateCowMergeOperation(ExtentForRange(24, 5), ExtentForRange(35, 5)), + CreateCowMergeOperation(ExtentForRange(30, 10), ExtentForRange(15, 10)), + }; + + FindDependency(transfers, &merge_after); + ASSERT_EQ(std::set<CowMergeOperation>({transfers[2]}), + merge_after.at(transfers[0])); + ASSERT_EQ(std::set<CowMergeOperation>({transfers[0], transfers[2]}), + merge_after.at(transfers[1])); + ASSERT_EQ(std::set<CowMergeOperation>({transfers[0], transfers[1]}), + merge_after.at(transfers[2])); +} + +TEST_F(MergeSequenceGeneratorTest, FindDependency_ReusedSourceBlocks) { + std::vector<CowMergeOperation> transfers = { + CreateCowMergeOperation(ExtentForRange(5, 10), ExtentForRange(15, 10)), + CreateCowMergeOperation(ExtentForRange(6, 5), ExtentForRange(30, 5)), + CreateCowMergeOperation(ExtentForRange(50, 5), ExtentForRange(5, 5)), + }; + + std::map<CowMergeOperation, std::set<CowMergeOperation>> merge_after; + FindDependency(transfers, &merge_after); + ASSERT_EQ(std::set<CowMergeOperation>({transfers[2]}), + merge_after.at(transfers[0])); + ASSERT_EQ(std::set<CowMergeOperation>({transfers[2]}), + merge_after.at(transfers[1])); +} + +TEST_F(MergeSequenceGeneratorTest, ValidateSequence) { + std::vector<CowMergeOperation> transfers = { + CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)), + CreateCowMergeOperation(ExtentForRange(30, 10), ExtentForRange(40, 10)), + }; + + // Self overlapping + ASSERT_TRUE(MergeSequenceGenerator::ValidateSequence(transfers)); + + transfers = { + CreateCowMergeOperation(ExtentForRange(30, 10), ExtentForRange(20, 10)), + CreateCowMergeOperation(ExtentForRange(15, 10), ExtentForRange(10, 10)), + }; + ASSERT_FALSE(MergeSequenceGenerator::ValidateSequence(transfers)); +} + +TEST_F(MergeSequenceGeneratorTest, GenerateSequenceNoCycles) { + std::vector<CowMergeOperation> transfers = { + CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)), + // file3 should merge before file2 + CreateCowMergeOperation(ExtentForRange(40, 5), ExtentForRange(25, 5)), + CreateCowMergeOperation(ExtentForRange(25, 10), ExtentForRange(30, 10)), + }; + + std::vector<CowMergeOperation> expected{ + transfers[0], transfers[2], transfers[1]}; + GenerateSequence(transfers, expected); +} + +TEST_F(MergeSequenceGeneratorTest, GenerateSequenceWithCycles) { + std::vector<CowMergeOperation> transfers = { + CreateCowMergeOperation(ExtentForRange(25, 10), ExtentForRange(30, 10)), + CreateCowMergeOperation(ExtentForRange(30, 10), ExtentForRange(40, 10)), + CreateCowMergeOperation(ExtentForRange(40, 10), ExtentForRange(25, 10)), + CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)), + }; + + // file 1,2,3 form a cycle. And file3, whose dst ext has smallest offset, will + // be converted to raw blocks + std::vector<CowMergeOperation> expected{ + transfers[3], transfers[1], transfers[0]}; + GenerateSequence(transfers, expected); +} + +TEST_F(MergeSequenceGeneratorTest, GenerateSequenceMultipleCycles) { + std::vector<CowMergeOperation> transfers = { + // cycle 1 + CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(25, 10)), + CreateCowMergeOperation(ExtentForRange(24, 5), ExtentForRange(35, 5)), + CreateCowMergeOperation(ExtentForRange(30, 10), ExtentForRange(15, 10)), + // cycle 2 + CreateCowMergeOperation(ExtentForRange(55, 10), ExtentForRange(60, 10)), + CreateCowMergeOperation(ExtentForRange(60, 10), ExtentForRange(70, 10)), + CreateCowMergeOperation(ExtentForRange(70, 10), ExtentForRange(55, 10)), + }; + + // file 3, 6 will be converted to raw. + std::vector<CowMergeOperation> expected{ + transfers[1], transfers[0], transfers[4], transfers[3]}; + GenerateSequence(transfers, expected); +} + +} // namespace chromeos_update_engine diff --git a/payload_generator/payload_file.cc b/payload_generator/payload_file.cc index b55d03c0..49dff4e7 100644 --- a/payload_generator/payload_file.cc +++ b/payload_generator/payload_file.cc @@ -78,17 +78,23 @@ bool PayloadFile::Init(const PayloadGenerationConfig& config) { *(manifest_.mutable_dynamic_partition_metadata()) = *(config.target.dynamic_partition_metadata); + if (config.is_partial_update) { + manifest_.set_partial_update(true); + } return true; } bool PayloadFile::AddPartition(const PartitionConfig& old_conf, const PartitionConfig& new_conf, - const vector<AnnotatedOperation>& aops) { + vector<AnnotatedOperation> aops, + vector<CowMergeOperation> merge_sequence) { Partition part; part.name = new_conf.name; - part.aops = aops; + part.aops = std::move(aops); + part.cow_merge_sequence = std::move(merge_sequence); part.postinstall = new_conf.postinstall; part.verity = new_conf.verity; + part.version = new_conf.version; // Initialize the PartitionInfo objects if present. if (!old_conf.path.empty()) TEST_AND_RETURN_FALSE( @@ -129,6 +135,9 @@ bool PayloadFile::WritePayload(const string& payload_file, for (const auto& part : part_vec_) { PartitionUpdate* partition = manifest_.add_partitions(); partition->set_partition_name(part.name); + if (!part.version.empty()) { + partition->set_version(part.version); + } if (part.postinstall.run) { partition->set_run_postinstall(true); if (!part.postinstall.path.empty()) @@ -156,6 +165,10 @@ bool PayloadFile::WritePayload(const string& payload_file, for (const AnnotatedOperation& aop : part.aops) { *partition->add_operations() = aop.op; } + for (const auto& merge_op : part.cow_merge_sequence) { + *partition->add_merge_operations() = merge_op; + } + if (part.old_info.has_size() || part.old_info.has_hash()) *(partition->mutable_old_partition_info()) = part.old_info; if (part.new_info.has_size() || part.new_info.has_hash()) @@ -167,16 +180,14 @@ bool PayloadFile::WritePayload(const string& payload_file, uint64_t signature_blob_length = 0; if (!private_key_path.empty()) { TEST_AND_RETURN_FALSE(PayloadSigner::SignatureBlobLength( - vector<string>(1, private_key_path), &signature_blob_length)); + {private_key_path}, &signature_blob_length)); PayloadSigner::AddSignatureToManifest( - next_blob_offset, - signature_blob_length, - &manifest_); + next_blob_offset, signature_blob_length, &manifest_); } // Serialize protobuf string serialized_manifest; - TEST_AND_RETURN_FALSE(manifest_.AppendToString(&serialized_manifest)); + TEST_AND_RETURN_FALSE(manifest_.SerializeToString(&serialized_manifest)); uint64_t metadata_size = sizeof(kDeltaMagic) + 2 * sizeof(uint64_t) + serialized_manifest.size(); @@ -215,13 +226,12 @@ bool PayloadFile::WritePayload(const string& payload_file, // Write metadata signature blob. if (!private_key_path.empty()) { - brillo::Blob metadata_hash, metadata_signature; + brillo::Blob metadata_hash; TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfFile( payload_file, metadata_size, &metadata_hash)); - TEST_AND_RETURN_FALSE( - PayloadSigner::SignHashWithKeys(metadata_hash, - vector<string>(1, private_key_path), - &metadata_signature)); + string metadata_signature; + TEST_AND_RETURN_FALSE(PayloadSigner::SignHashWithKeys( + metadata_hash, {private_key_path}, &metadata_signature)); TEST_AND_RETURN_FALSE_ERRNO( writer.Write(metadata_signature.data(), metadata_signature.size())); } @@ -245,16 +255,16 @@ bool PayloadFile::WritePayload(const string& payload_file, // Write payload signature blob. if (!private_key_path.empty()) { LOG(INFO) << "Signing the update..."; - brillo::Blob signature_blob; + string signature; TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload( payload_file, - vector<string>(1, private_key_path), + {private_key_path}, metadata_size, metadata_signature_size, metadata_size + metadata_signature_size + manifest_.signatures_offset(), - &signature_blob)); + &signature)); TEST_AND_RETURN_FALSE_ERRNO( - writer.Write(signature_blob.data(), signature_blob.size())); + writer.Write(signature.data(), signature.size())); } ReportPayloadUsage(metadata_size); @@ -330,15 +340,15 @@ void PayloadFile::ReportPayloadUsage(uint64_t metadata_size) const { const DeltaObject& object = object_count.first; // Use printf() instead of LOG(INFO) because timestamp makes it difficult to // compare two reports. - printf( - kFormatString, - object.size * 100.0 / total_size, - object.size, - (object.type >= 0 ? InstallOperationTypeName( - static_cast<InstallOperation_Type>(object.type)) - : "-"), - object.name.c_str(), - object_count.second); + printf(kFormatString, + object.size * 100.0 / total_size, + object.size, + (object.type >= 0 + ? InstallOperationTypeName( + static_cast<InstallOperation::Type>(object.type)) + : "-"), + object.name.c_str(), + object_count.second); } printf(kFormatString, 100.0, total_size, "", "<total>", total_op); fflush(stdout); diff --git a/payload_generator/payload_file.h b/payload_generator/payload_file.h index 9dc80a72..8b179569 100644 --- a/payload_generator/payload_file.h +++ b/payload_generator/payload_file.h @@ -43,7 +43,8 @@ class PayloadFile { // reference a blob stored in the file provided to WritePayload(). bool AddPartition(const PartitionConfig& old_conf, const PartitionConfig& new_conf, - const std::vector<AnnotatedOperation>& aops); + std::vector<AnnotatedOperation> aops, + std::vector<CowMergeOperation> merge_sequence); // Write the payload to the |payload_file| file. The operations reference // blobs in the |data_blobs_path| file and the blobs will be reordered in the @@ -60,9 +61,9 @@ class PayloadFile { // Computes a SHA256 hash of the given buf and sets the hash value in the // operation so that update_engine could verify. This hash should be set // for all operations that have a non-zero data blob. One exception is the - // dummy operation for signature blob because the contents of the signature + // fake operation for signature blob because the contents of the signature // blob will not be available at payload creation time. So, update_engine will - // gracefully ignore the dummy signature operation. + // gracefully ignore the fake signature operation. static bool AddOperationHash(InstallOperation* op, const brillo::Blob& buf); // Install operations in the manifest may reference data blobs, which @@ -90,12 +91,15 @@ class PayloadFile { // The operations to be performed to this partition. std::vector<AnnotatedOperation> aops; + std::vector<CowMergeOperation> cow_merge_sequence; PartitionInfo old_info; PartitionInfo new_info; PostInstallConfig postinstall; VerityConfig verity; + // Per partition timestamp. + std::string version; }; std::vector<Partition> part_vec_; diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc index 71587963..35a95dc0 100644 --- a/payload_generator/payload_generation_config.cc +++ b/payload_generator/payload_generation_config.cc @@ -150,25 +150,34 @@ bool ImageConfig::LoadDynamicPartitionMetadata( for (const auto& group_name : group_names) { DynamicPartitionGroup* group = metadata->add_groups(); group->set_name(group_name); - if (!store.GetString(group_name + "_size", &buf)) { - LOG(ERROR) << "Missing " << group_name + "_size."; + if (!store.GetString("super_" + group_name + "_group_size", &buf) && + !store.GetString(group_name + "_size", &buf)) { + LOG(ERROR) << "Missing super_" << group_name + "_group_size or " + << group_name << "_size."; return false; } uint64_t max_size; if (!base::StringToUint64(buf, &max_size)) { - LOG(ERROR) << group_name << "_size=" << buf << " is not an integer."; + LOG(ERROR) << "Group size for " << group_name << " = " << buf + << " is not an integer."; return false; } group->set_size(max_size); - if (store.GetString(group_name + "_partition_list", &buf)) { + if (store.GetString("super_" + group_name + "_partition_list", &buf) || + store.GetString(group_name + "_partition_list", &buf)) { auto partition_names = brillo::string_utils::Split(buf, " "); for (const auto& partition_name : partition_names) { group->add_partition_names()->assign(partition_name); } } } + + bool snapshot_enabled = false; + store.GetBoolean("virtual_ab", &snapshot_enabled); + metadata->set_snapshot_enabled(snapshot_enabled); + dynamic_partition_metadata = std::move(metadata); return true; } @@ -225,11 +234,12 @@ bool PayloadVersion::Validate() const { minor == kOpSrcHashMinorPayloadVersion || minor == kBrotliBsdiffMinorPayloadVersion || minor == kPuffdiffMinorPayloadVersion || - minor == kVerityMinorPayloadVersion); + minor == kVerityMinorPayloadVersion || + minor == kPartialUpdateMinorPayloadVersion); return true; } -bool PayloadVersion::OperationAllowed(InstallOperation_Type operation) const { +bool PayloadVersion::OperationAllowed(InstallOperation::Type operation) const { switch (operation) { // Full operations: case InstallOperation::REPLACE: @@ -260,13 +270,14 @@ bool PayloadVersion::OperationAllowed(InstallOperation_Type operation) const { return false; } -bool PayloadVersion::IsDelta() const { +bool PayloadVersion::IsDeltaOrPartial() const { return minor != kFullPayloadMinorVersion; } bool PayloadGenerationConfig::Validate() const { TEST_AND_RETURN_FALSE(version.Validate()); - TEST_AND_RETURN_FALSE(version.IsDelta() == is_delta); + TEST_AND_RETURN_FALSE(version.IsDeltaOrPartial() == + (is_delta || is_partial_update)); if (is_delta) { for (const PartitionConfig& part : source.partitions) { if (!part.path.empty()) { @@ -294,6 +305,10 @@ bool PayloadGenerationConfig::Validate() const { TEST_AND_RETURN_FALSE(part.verity.IsEmpty()); } + if (version.minor < kPartialUpdateMinorPayloadVersion) { + TEST_AND_RETURN_FALSE(!is_partial_update); + } + TEST_AND_RETURN_FALSE(hard_chunk_size == -1 || hard_chunk_size % block_size == 0); TEST_AND_RETURN_FALSE(soft_chunk_size % block_size == 0); diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h index 32f12292..ec630435 100644 --- a/payload_generator/payload_generation_config.h +++ b/payload_generator/payload_generation_config.h @@ -116,6 +116,12 @@ struct PartitionConfig { PostInstallConfig postinstall; VerityConfig verity; + + // Enables the on device fec data computation by default. + bool disable_fec_computation = false; + + // Per-partition version, usually a number representing timestamp. + std::string version; }; // The ImageConfig struct describes a pair of binaries kernel and rootfs and the @@ -165,10 +171,10 @@ struct PayloadVersion { bool Validate() const; // Return whether the passed |operation| is allowed by this payload. - bool OperationAllowed(InstallOperation_Type operation) const; + bool OperationAllowed(InstallOperation::Type operation) const; - // Whether this payload version is a delta payload. - bool IsDelta() const; + // Whether this payload version is a delta or partial payload. + bool IsDeltaOrPartial() const; // The major version of the payload. uint64_t major; @@ -195,6 +201,10 @@ struct PayloadGenerationConfig { // Whether the requested payload is a delta payload. bool is_delta = false; + // Whether the requested payload is a partial payload, i.e. only update a + // subset of partitions on device. + bool is_partial_update = false; + // The major/minor version of the payload. PayloadVersion version; diff --git a/payload_generator/payload_generation_config_android.cc b/payload_generator/payload_generation_config_android.cc index 90c053f4..d950092b 100644 --- a/payload_generator/payload_generation_config_android.cc +++ b/payload_generator/payload_generation_config_android.cc @@ -63,11 +63,13 @@ bool AvbDescriptorCallback(const AvbDescriptor* descriptor, void* user_data) { part->verity.hash_tree_extent = ExtentForBytes( hashtree.hash_block_size, hashtree.tree_offset, hashtree.tree_size); - part->verity.fec_data_extent = - ExtentForBytes(hashtree.data_block_size, 0, hashtree.fec_offset); - part->verity.fec_extent = ExtentForBytes( - hashtree.data_block_size, hashtree.fec_offset, hashtree.fec_size); - part->verity.fec_roots = hashtree.fec_num_roots; + if (!part->disable_fec_computation) { + part->verity.fec_data_extent = + ExtentForBytes(hashtree.data_block_size, 0, hashtree.fec_offset); + part->verity.fec_extent = ExtentForBytes( + hashtree.data_block_size, hashtree.fec_offset, hashtree.fec_size); + part->verity.fec_roots = hashtree.fec_num_roots; + } return true; } @@ -205,7 +207,8 @@ bool ImageConfig::LoadVerityConfig() { ExtentForRange(hash_start_block, tree_size / block_size); } fec_ecc_metadata ecc_data; - if (fh.get_ecc_metadata(ecc_data) && ecc_data.valid) { + if (!part.disable_fec_computation && fh.get_ecc_metadata(ecc_data) && + ecc_data.valid) { TEST_AND_RETURN_FALSE(block_size == FEC_BLOCKSIZE); part.verity.fec_data_extent = ExtentForRange(0, ecc_data.blocks); part.verity.fec_extent = diff --git a/payload_generator/payload_generation_config_android_unittest.cc b/payload_generator/payload_generation_config_android_unittest.cc index 53378c22..44eaf55e 100644 --- a/payload_generator/payload_generation_config_android_unittest.cc +++ b/payload_generator/payload_generation_config_android_unittest.cc @@ -160,6 +160,24 @@ TEST_F(PayloadGenerationConfigAndroidTest, LoadVerityConfigSimpleTest) { EXPECT_EQ(2u, verity.fec_roots); } +TEST_F(PayloadGenerationConfigAndroidTest, LoadVerityConfigDisableFecTest) { + brillo::Blob part = GetAVBPartition(); + test_utils::WriteFileVector(temp_file_.path(), part); + image_config_.partitions[0].disable_fec_computation = true; + EXPECT_TRUE(image_config_.LoadImageSize()); + EXPECT_TRUE(image_config_.partitions[0].OpenFilesystem()); + EXPECT_TRUE(image_config_.LoadVerityConfig()); + const VerityConfig& verity = image_config_.partitions[0].verity; + EXPECT_FALSE(verity.IsEmpty()); + EXPECT_EQ(ExtentForRange(0, 2), verity.hash_tree_data_extent); + EXPECT_EQ(ExtentForRange(2, 1), verity.hash_tree_extent); + EXPECT_EQ("sha1", verity.hash_tree_algorithm); + brillo::Blob salt(kHashTreeSalt, std::end(kHashTreeSalt)); + EXPECT_EQ(salt, verity.hash_tree_salt); + EXPECT_EQ(0u, verity.fec_data_extent.num_blocks()); + EXPECT_EQ(0u, verity.fec_extent.num_blocks()); +} + TEST_F(PayloadGenerationConfigAndroidTest, LoadVerityConfigInvalidHashTreeTest) { brillo::Blob part = GetAVBPartition(); diff --git a/payload_generator/payload_generation_config_unittest.cc b/payload_generator/payload_generation_config_unittest.cc index 70a3df31..aca9655d 100644 --- a/payload_generator/payload_generation_config_unittest.cc +++ b/payload_generator/payload_generation_config_unittest.cc @@ -59,7 +59,7 @@ TEST_F(PayloadGenerationConfigTest, LoadDynamicPartitionMetadataTest) { ASSERT_TRUE( store.LoadFromString("super_partition_groups=group_a group_b\n" "group_a_size=3221225472\n" - "group_a_partition_list=system product_services\n" + "group_a_partition_list=system system_ext\n" "group_b_size=2147483648\n" "group_b_partition_list=vendor\n")); EXPECT_TRUE(image_config.LoadDynamicPartitionMetadata(store)); @@ -72,7 +72,7 @@ TEST_F(PayloadGenerationConfigTest, LoadDynamicPartitionMetadataTest) { EXPECT_EQ(3221225472u, group_a.size()); ASSERT_EQ(2, group_a.partition_names_size()); EXPECT_EQ("system", group_a.partition_names(0)); - EXPECT_EQ("product_services", group_a.partition_names(1)); + EXPECT_EQ("system_ext", group_a.partition_names(1)); const auto& group_b = image_config.dynamic_partition_metadata->groups(1); EXPECT_EQ("group_b", group_b.name()); @@ -108,17 +108,17 @@ TEST_F(PayloadGenerationConfigTest, ValidateDynamicPartitionMetadata) { PartitionConfig system("system"); system.size = 2147483648u; - PartitionConfig product_services("product_services"); - product_services.size = 1073741824u; + PartitionConfig system_ext("system_ext"); + system_ext.size = 1073741824u; image_config.partitions.push_back(std::move(system)); - image_config.partitions.push_back(std::move(product_services)); + image_config.partitions.push_back(std::move(system_ext)); brillo::KeyValueStore store; ASSERT_TRUE( store.LoadFromString("super_partition_groups=foo\n" "foo_size=3221225472\n" - "foo_partition_list=system product_services\n")); + "foo_partition_list=system system_ext\n")); EXPECT_TRUE(image_config.LoadDynamicPartitionMetadata(store)); EXPECT_NE(nullptr, image_config.dynamic_partition_metadata); diff --git a/payload_generator/payload_properties_unittest.cc b/payload_generator/payload_properties_unittest.cc index db3902ce..e0072fc6 100644 --- a/payload_generator/payload_properties_unittest.cc +++ b/payload_generator/payload_properties_unittest.cc @@ -98,7 +98,7 @@ class PayloadPropertiesTest : public ::testing::Test { EXPECT_TRUE(strategy->GenerateOperations( config, old_part, new_part, &blob_file_writer, &aops)); - payload.AddPartition(old_part, new_part, aops); + payload.AddPartition(old_part, new_part, aops, {}); uint64_t metadata_size; EXPECT_TRUE(payload.WritePayload( diff --git a/payload_generator/payload_signer.cc b/payload_generator/payload_signer.cc index 420329ff..9a44f947 100644 --- a/payload_generator/payload_signer.cc +++ b/payload_generator/payload_signer.cc @@ -18,6 +18,7 @@ #include <endian.h> +#include <memory> #include <utility> #include <base/logging.h> @@ -28,6 +29,7 @@ #include <openssl/err.h> #include <openssl/pem.h> +#include "update_engine/common/constants.h" #include "update_engine/common/hash_calculator.h" #include "update_engine/common/subprocess.h" #include "update_engine/common/utils.h" @@ -45,45 +47,49 @@ using std::vector; namespace chromeos_update_engine { namespace { - -// The payload verifier will check all the signatures included in the payload -// regardless of the version field. Old version of the verifier require the -// version field to be included and be 1. -const uint32_t kSignatureMessageLegacyVersion = 1; - // Given raw |signatures|, packs them into a protobuf and serializes it into a -// binary blob. Returns true on success, false otherwise. -bool ConvertSignatureToProtobufBlob(const vector<brillo::Blob>& signatures, - brillo::Blob* out_signature_blob) { +// string. Returns true on success, false otherwise. +bool ConvertSignaturesToProtobuf(const vector<brillo::Blob>& signatures, + const vector<size_t>& padded_signature_sizes, + string* out_serialized_signature) { + TEST_AND_RETURN_FALSE(signatures.size() == padded_signature_sizes.size()); // Pack it into a protobuf Signatures out_message; - for (const brillo::Blob& signature : signatures) { - Signatures_Signature* sig_message = out_message.add_signatures(); - // Set all the signatures with the same version number. - sig_message->set_version(kSignatureMessageLegacyVersion); - sig_message->set_data(signature.data(), signature.size()); + for (size_t i = 0; i < signatures.size(); i++) { + const auto& signature = signatures[i]; + const auto& padded_signature_size = padded_signature_sizes[i]; + TEST_AND_RETURN_FALSE(padded_signature_size >= signature.size()); + Signatures::Signature* sig_message = out_message.add_signatures(); + // Skip assigning the same version number because we don't need to be + // compatible with old major version 1 client anymore. + + // TODO(Xunchang) don't need to set the unpadded_signature_size field for + // RSA key signed signatures. + sig_message->set_unpadded_signature_size(signature.size()); + brillo::Blob padded_signature = signature; + padded_signature.insert( + padded_signature.end(), padded_signature_size - signature.size(), 0); + sig_message->set_data(padded_signature.data(), padded_signature.size()); } // Serialize protobuf - string serialized; - TEST_AND_RETURN_FALSE(out_message.AppendToString(&serialized)); - out_signature_blob->insert( - out_signature_blob->end(), serialized.begin(), serialized.end()); - LOG(INFO) << "Signature blob size: " << out_signature_blob->size(); + TEST_AND_RETURN_FALSE( + out_message.SerializeToString(out_serialized_signature)); + LOG(INFO) << "Signature blob size: " << out_serialized_signature->size(); return true; } -// Given an unsigned payload under |payload_path| and the |signature_blob| and -// |metadata_signature_blob| generates an updated payload that includes the +// Given an unsigned payload under |payload_path| and the |payload_signature| +// and |metadata_signature| generates an updated payload that includes the // signatures. It populates |out_metadata_size| with the size of the final -// manifest after adding the dummy signature operation, and +// manifest after adding the fake signature operation, and // |out_signatures_offset| with the expected offset for the new blob, and -// |out_metadata_signature_size| which will be size of |metadata_signature_blob| +// |out_metadata_signature_size| which will be size of |metadata_signature| // if the payload major version supports metadata signature, 0 otherwise. // Returns true on success, false otherwise. bool AddSignatureBlobToPayload(const string& payload_path, - const brillo::Blob& signature_blob, - const brillo::Blob& metadata_signature_blob, + const string& payload_signature, + const string& metadata_signature, brillo::Blob* out_payload, uint64_t* out_metadata_size, uint32_t* out_metadata_signature_size, @@ -99,7 +105,7 @@ bool AddSignatureBlobToPayload(const string& payload_path, uint32_t metadata_signature_size = payload_metadata.GetMetadataSignatureSize(); // Write metadata signature size in header. - uint32_t metadata_signature_size_be = htobe32(metadata_signature_blob.size()); + uint32_t metadata_signature_size_be = htobe32(metadata_signature.size()); memcpy(payload.data() + manifest_offset, &metadata_signature_size_be, sizeof(metadata_signature_size_be)); @@ -108,9 +114,9 @@ bool AddSignatureBlobToPayload(const string& payload_path, payload.erase(payload.begin() + metadata_size, payload.begin() + metadata_size + metadata_signature_size); payload.insert(payload.begin() + metadata_size, - metadata_signature_blob.begin(), - metadata_signature_blob.end()); - metadata_signature_size = metadata_signature_blob.size(); + metadata_signature.begin(), + metadata_signature.end()); + metadata_signature_size = metadata_signature.size(); LOG(INFO) << "Metadata signature size: " << metadata_signature_size; DeltaArchiveManifest manifest; @@ -122,10 +128,10 @@ bool AddSignatureBlobToPayload(const string& payload_path, // contents. We don't allow the manifest to change if there is already an op // present, because that might invalidate previously generated // hashes/signatures. - if (manifest.signatures_size() != signature_blob.size()) { + if (manifest.signatures_size() != payload_signature.size()) { LOG(ERROR) << "Attempt to insert different signature sized blob. " << "(current:" << manifest.signatures_size() - << "new:" << signature_blob.size() << ")"; + << "new:" << payload_signature.size() << ")"; return false; } @@ -134,7 +140,7 @@ bool AddSignatureBlobToPayload(const string& payload_path, // Updates the manifest to include the signature operation. PayloadSigner::AddSignatureToManifest( payload.size() - metadata_size - metadata_signature_size, - signature_blob.size(), + payload_signature.size(), &manifest); // Updates the payload to include the new manifest. @@ -160,8 +166,8 @@ bool AddSignatureBlobToPayload(const string& payload_path, LOG(INFO) << "Signature Blob Offset: " << signatures_offset; payload.resize(signatures_offset); payload.insert(payload.begin() + signatures_offset, - signature_blob.begin(), - signature_blob.end()); + payload_signature.begin(), + payload_signature.end()); *out_payload = std::move(payload); *out_metadata_size = metadata_size; @@ -201,8 +207,35 @@ bool CalculateHashFromPayload(const brillo::Blob& payload, return true; } +std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> CreatePrivateKeyFromPath( + const string& private_key_path) { + FILE* fprikey = fopen(private_key_path.c_str(), "rb"); + if (!fprikey) { + PLOG(ERROR) << "Failed to read " << private_key_path; + return {nullptr, nullptr}; + } + + auto private_key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>( + PEM_read_PrivateKey(fprikey, nullptr, nullptr, nullptr), EVP_PKEY_free); + fclose(fprikey); + return private_key; +} + } // namespace +bool PayloadSigner::GetMaximumSignatureSize(const string& private_key_path, + size_t* signature_size) { + *signature_size = 0; + auto private_key = CreatePrivateKeyFromPath(private_key_path); + if (!private_key) { + LOG(ERROR) << "Failed to create private key from " << private_key_path; + return false; + } + + *signature_size = EVP_PKEY_size(private_key.get()); + return true; +} + void PayloadSigner::AddSignatureToManifest(uint64_t signature_blob_offset, uint64_t signature_blob_length, DeltaArchiveManifest* manifest) { @@ -236,21 +269,22 @@ bool PayloadSigner::VerifySignedPayload(const string& payload_path, signatures_offset, &payload_hash, &metadata_hash)); - brillo::Blob signature_blob(payload.begin() + signatures_offset, - payload.end()); + string signature(payload.begin() + signatures_offset, payload.end()); string public_key; TEST_AND_RETURN_FALSE(utils::ReadFile(public_key_path, &public_key)); - TEST_AND_RETURN_FALSE(PayloadVerifier::PadRSA2048SHA256Hash(&payload_hash)); - TEST_AND_RETURN_FALSE(PayloadVerifier::VerifySignature( - signature_blob, public_key, payload_hash)); + TEST_AND_RETURN_FALSE(payload_hash.size() == kSHA256Size); + + auto payload_verifier = PayloadVerifier::CreateInstance(public_key); + TEST_AND_RETURN_FALSE(payload_verifier != nullptr); + + TEST_AND_RETURN_FALSE( + payload_verifier->VerifySignature(signature, payload_hash)); if (metadata_signature_size) { - signature_blob.assign( - payload.begin() + metadata_size, - payload.begin() + metadata_size + metadata_signature_size); + signature.assign(payload.begin() + metadata_size, + payload.begin() + metadata_size + metadata_signature_size); + TEST_AND_RETURN_FALSE(metadata_hash.size() == kSHA256Size); TEST_AND_RETURN_FALSE( - PayloadVerifier::PadRSA2048SHA256Hash(&metadata_hash)); - TEST_AND_RETURN_FALSE(PayloadVerifier::VerifySignature( - signature_blob, public_key, metadata_hash)); + payload_verifier->VerifySignature(signature, metadata_hash)); } return true; } @@ -260,49 +294,97 @@ bool PayloadSigner::SignHash(const brillo::Blob& hash, brillo::Blob* out_signature) { LOG(INFO) << "Signing hash with private key: " << private_key_path; // We expect unpadded SHA256 hash coming in - TEST_AND_RETURN_FALSE(hash.size() == 32); - brillo::Blob padded_hash(hash); - PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash); - + TEST_AND_RETURN_FALSE(hash.size() == kSHA256Size); // The code below executes the equivalent of: // // openssl rsautl -raw -sign -inkey |private_key_path| // -in |padded_hash| -out |out_signature| - FILE* fprikey = fopen(private_key_path.c_str(), "rb"); - TEST_AND_RETURN_FALSE(fprikey != nullptr); - RSA* rsa = PEM_read_RSAPrivateKey(fprikey, nullptr, nullptr, nullptr); - fclose(fprikey); - TEST_AND_RETURN_FALSE(rsa != nullptr); - brillo::Blob signature(RSA_size(rsa)); - ssize_t signature_size = RSA_private_encrypt(padded_hash.size(), - padded_hash.data(), - signature.data(), - rsa, - RSA_NO_PADDING); - RSA_free(rsa); - if (signature_size < 0) { - LOG(ERROR) << "Signing hash failed: " - << ERR_error_string(ERR_get_error(), nullptr); + auto private_key = CreatePrivateKeyFromPath(private_key_path); + if (!private_key) { + LOG(ERROR) << "Failed to create private key from " << private_key_path; + return false; + } + + int key_type = EVP_PKEY_id(private_key.get()); + brillo::Blob signature; + if (key_type == EVP_PKEY_RSA) { + // TODO(b/158580694): Switch back to get0 version and remove manual freeing + // of the object once the bug is resolved or gale has been moved to + // informational. + RSA* rsa = EVP_PKEY_get1_RSA(private_key.get()); + TEST_AND_RETURN_FALSE(rsa != nullptr); + + brillo::Blob padded_hash = hash; + PayloadVerifier::PadRSASHA256Hash(&padded_hash, RSA_size(rsa)); + + signature.resize(RSA_size(rsa)); + ssize_t signature_size = RSA_private_encrypt(padded_hash.size(), + padded_hash.data(), + signature.data(), + rsa, + RSA_NO_PADDING); + if (signature_size < 0) { + LOG(ERROR) << "Signing hash failed: " + << ERR_error_string(ERR_get_error(), nullptr); + RSA_free(rsa); + return false; + } + RSA_free(rsa); + TEST_AND_RETURN_FALSE(static_cast<size_t>(signature_size) == + signature.size()); + } else if (key_type == EVP_PKEY_EC) { + // TODO(b/158580694): Switch back to get0 version and remove manual freeing + // of the object once the bug is resolved or gale has been moved to + // informational. + EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(private_key.get()); + TEST_AND_RETURN_FALSE(ec_key != nullptr); + + signature.resize(ECDSA_size(ec_key)); + unsigned int signature_size; + if (ECDSA_sign(0, + hash.data(), + hash.size(), + signature.data(), + &signature_size, + ec_key) != 1) { + LOG(ERROR) << "Signing hash failed: " + << ERR_error_string(ERR_get_error(), nullptr); + EC_KEY_free(ec_key); + return false; + } + EC_KEY_free(ec_key); + + // NIST P-256 + LOG(ERROR) << "signature max size " << signature.size() << " size " + << signature_size; + TEST_AND_RETURN_FALSE(signature.size() >= signature_size); + signature.resize(signature_size); + } else { + LOG(ERROR) << "key_type " << key_type << " isn't supported for signing"; return false; } - TEST_AND_RETURN_FALSE(static_cast<size_t>(signature_size) == - signature.size()); out_signature->swap(signature); return true; } bool PayloadSigner::SignHashWithKeys(const brillo::Blob& hash_data, const vector<string>& private_key_paths, - brillo::Blob* out_signature_blob) { + string* out_serialized_signature) { vector<brillo::Blob> signatures; + vector<size_t> padded_signature_sizes; for (const string& path : private_key_paths) { brillo::Blob signature; TEST_AND_RETURN_FALSE(SignHash(hash_data, path, &signature)); signatures.push_back(signature); + + size_t padded_signature_size; + TEST_AND_RETURN_FALSE( + GetMaximumSignatureSize(path, &padded_signature_size)); + padded_signature_sizes.push_back(padded_signature_size); } - TEST_AND_RETURN_FALSE( - ConvertSignatureToProtobufBlob(signatures, out_signature_blob)); + TEST_AND_RETURN_FALSE(ConvertSignaturesToProtobuf( + signatures, padded_signature_sizes, out_serialized_signature)); return true; } @@ -311,7 +393,7 @@ bool PayloadSigner::SignPayload(const string& unsigned_payload_path, const uint64_t metadata_size, const uint32_t metadata_signature_size, const uint64_t signatures_offset, - brillo::Blob* out_signature_blob) { + string* out_serialized_signature) { brillo::Blob payload; TEST_AND_RETURN_FALSE(utils::ReadFile(unsigned_payload_path, &payload)); brillo::Blob hash_data; @@ -322,16 +404,16 @@ bool PayloadSigner::SignPayload(const string& unsigned_payload_path, &hash_data, nullptr)); TEST_AND_RETURN_FALSE( - SignHashWithKeys(hash_data, private_key_paths, out_signature_blob)); + SignHashWithKeys(hash_data, private_key_paths, out_serialized_signature)); return true; } bool PayloadSigner::SignatureBlobLength(const vector<string>& private_key_paths, uint64_t* out_length) { DCHECK(out_length); - brillo::Blob x_blob(1, 'x'), hash_blob, sig_blob; - TEST_AND_RETURN_FALSE( - HashCalculator::RawHashOfBytes(x_blob.data(), x_blob.size(), &hash_blob)); + brillo::Blob hash_blob; + TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfData({'x'}, &hash_blob)); + string sig_blob; TEST_AND_RETURN_FALSE( SignHashWithKeys(hash_blob, private_key_paths, &sig_blob)); *out_length = sig_blob.size(); @@ -339,7 +421,7 @@ bool PayloadSigner::SignatureBlobLength(const vector<string>& private_key_paths, } bool PayloadSigner::HashPayloadForSigning(const string& payload_path, - const vector<int>& signature_sizes, + const vector<size_t>& signature_sizes, brillo::Blob* out_payload_hash_data, brillo::Blob* out_metadata_hash) { // Create a signature blob with signatures filled with 0. @@ -348,17 +430,17 @@ bool PayloadSigner::HashPayloadForSigning(const string& payload_path, for (int signature_size : signature_sizes) { signatures.emplace_back(signature_size, 0); } - brillo::Blob signature_blob; + string signature; TEST_AND_RETURN_FALSE( - ConvertSignatureToProtobufBlob(signatures, &signature_blob)); + ConvertSignaturesToProtobuf(signatures, signature_sizes, &signature)); brillo::Blob payload; uint64_t metadata_size, signatures_offset; uint32_t metadata_signature_size; // Prepare payload for hashing. TEST_AND_RETURN_FALSE(AddSignatureBlobToPayload(payload_path, - signature_blob, - signature_blob, + signature, + signature, &payload, &metadata_size, &metadata_signature_size, @@ -374,6 +456,7 @@ bool PayloadSigner::HashPayloadForSigning(const string& payload_path, bool PayloadSigner::AddSignatureToPayload( const string& payload_path, + const vector<size_t>& padded_signature_sizes, const vector<brillo::Blob>& payload_signatures, const vector<brillo::Blob>& metadata_signatures, const string& signed_payload_path, @@ -381,19 +464,19 @@ bool PayloadSigner::AddSignatureToPayload( // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory. // Loads the payload and adds the signature op to it. - brillo::Blob signature_blob, metadata_signature_blob; - TEST_AND_RETURN_FALSE( - ConvertSignatureToProtobufBlob(payload_signatures, &signature_blob)); + string payload_signature, metadata_signature; + TEST_AND_RETURN_FALSE(ConvertSignaturesToProtobuf( + payload_signatures, padded_signature_sizes, &payload_signature)); if (!metadata_signatures.empty()) { - TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob( - metadata_signatures, &metadata_signature_blob)); + TEST_AND_RETURN_FALSE(ConvertSignaturesToProtobuf( + metadata_signatures, padded_signature_sizes, &metadata_signature)); } brillo::Blob payload; uint64_t signatures_offset; uint32_t metadata_signature_size; TEST_AND_RETURN_FALSE(AddSignatureBlobToPayload(payload_path, - signature_blob, - metadata_signature_blob, + payload_signature, + metadata_signature, &payload, out_metadata_size, &metadata_signature_size, diff --git a/payload_generator/payload_signer.h b/payload_generator/payload_signer.h index 71f4983a..9676b718 100644 --- a/payload_generator/payload_signer.h +++ b/payload_generator/payload_signer.h @@ -51,27 +51,27 @@ class PayloadSigner { brillo::Blob* out_signature); // Sign |hash_data| blob with all private keys in |private_key_paths|, then - // convert the signatures to protobuf blob. + // convert the signatures to serialized protobuf. static bool SignHashWithKeys( const brillo::Blob& hash_data, const std::vector<std::string>& private_key_paths, - brillo::Blob* out_signature_blob); + std::string* out_serialized_signature); // Given an unsigned payload in |unsigned_payload_path|, private keys in // |private_key_path|, metadata size in |metadata_size|, metadata signature // size in |metadata_signature_size| and signatures offset in // |signatures_offset|, calculates the payload signature blob into - // |out_signature_blob|. Note that the payload must already have an - // updated manifest that includes the dummy signature op and correct metadata + // |out_serialized_signature|. Note that the payload must already have an + // updated manifest that includes the fake signature op and correct metadata // signature size in header. Returns true on success, false otherwise. static bool SignPayload(const std::string& unsigned_payload_path, const std::vector<std::string>& private_key_paths, const uint64_t metadata_size, const uint32_t metadata_signature_size, const uint64_t signatures_offset, - brillo::Blob* out_signature_blob); + std::string* out_serialized_signature); - // Returns the length of out_signature_blob that will result in a call + // Returns the length of out_serialized_signature that will result in a call // to SignPayload with the given private keys. Returns true on success. static bool SignatureBlobLength( const std::vector<std::string>& private_key_paths, uint64_t* out_length); @@ -88,11 +88,11 @@ class PayloadSigner { // // The changes to payload are not preserved or written to disk. static bool HashPayloadForSigning(const std::string& payload_path, - const std::vector<int>& signature_sizes, + const std::vector<size_t>& signature_sizes, brillo::Blob* out_payload_hash_data, brillo::Blob* out_metadata_hash); - // Given an unsigned payload in |payload_path| (with no dummy signature op) + // Given an unsigned payload in |payload_path| (with no fake signature op) // and the raw |payload_signatures| and |metadata_signatures| updates the // payload to include the signature thus turning it into a signed payload. The // new payload is stored in |signed_payload_path|. |payload_path| and @@ -102,6 +102,7 @@ class PayloadSigner { // otherwise. static bool AddSignatureToPayload( const std::string& payload_path, + const std::vector<size_t>& padded_signature_sizes, const std::vector<brillo::Blob>& payload_signatures, const std::vector<brillo::Blob>& metadata_signatures, const std::string& signed_payload_path, @@ -116,6 +117,16 @@ class PayloadSigner { const std::string& private_key_path, std::string* out_signature); + static bool ExtractPayloadProperties(const std::string& payload_path, + brillo::KeyValueStore* properties); + + // This function calculates the maximum size, in bytes, of a signature signed + // by private_key_path. For an RSA key, this returns the number of bytes + // needed to represent the modulus. For an EC key, this returns the maximum + // size of a DER-encoded ECDSA signature. + static bool GetMaximumSignatureSize(const std::string& private_key_path, + size_t* signature_size); + private: // This should never be constructed DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadSigner); diff --git a/payload_generator/payload_signer_unittest.cc b/payload_generator/payload_signer_unittest.cc index f7f9c696..fe629977 100644 --- a/payload_generator/payload_signer_unittest.cc +++ b/payload_generator/payload_signer_unittest.cc @@ -45,6 +45,10 @@ const char* kUnittestPrivateKeyPath = "unittest_key.pem"; const char* kUnittestPublicKeyPath = "unittest_key.pub.pem"; const char* kUnittestPrivateKey2Path = "unittest_key2.pem"; const char* kUnittestPublicKey2Path = "unittest_key2.pub.pem"; +const char* kUnittestPrivateKeyRSA4096Path = "unittest_key_RSA4096.pem"; +const char* kUnittestPublicKeyRSA4096Path = "unittest_key_RSA4096.pub.pem"; +const char* kUnittestPrivateKeyECPath = "unittest_key_EC.pem"; +const char* kUnittestPublicKeyECPath = "unittest_key_EC.pub.pem"; // Some data and its corresponding hash and signature: const char kDataToSign[] = "This is some data to sign."; @@ -87,44 +91,34 @@ const uint8_t kDataSignature[] = { 0x43, 0xb9, 0xab, 0x7d}; namespace { -void SignSampleData(brillo::Blob* out_signature_blob, - const vector<string>& private_keys) { - brillo::Blob data_blob(std::begin(kDataToSign), - std::begin(kDataToSign) + strlen(kDataToSign)); +void SignSampleData(string* out_signature, const vector<string>& private_keys) { uint64_t length = 0; EXPECT_TRUE(PayloadSigner::SignatureBlobLength(private_keys, &length)); EXPECT_GT(length, 0U); brillo::Blob hash_blob; EXPECT_TRUE(HashCalculator::RawHashOfBytes( - data_blob.data(), data_blob.size(), &hash_blob)); - EXPECT_TRUE(PayloadSigner::SignHashWithKeys( - hash_blob, private_keys, out_signature_blob)); - EXPECT_EQ(length, out_signature_blob->size()); + kDataToSign, strlen(kDataToSign), &hash_blob)); + EXPECT_TRUE( + PayloadSigner::SignHashWithKeys(hash_blob, private_keys, out_signature)); + EXPECT_EQ(length, out_signature->size()); } } // namespace class PayloadSignerTest : public ::testing::Test { protected: - void SetUp() override { - PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash_data_); - } - - brillo::Blob padded_hash_data_{std::begin(kDataHash), std::end(kDataHash)}; + brillo::Blob hash_data_{std::begin(kDataHash), std::end(kDataHash)}; }; TEST_F(PayloadSignerTest, SignSimpleTextTest) { - brillo::Blob signature_blob; - SignSampleData(&signature_blob, - {GetBuildArtifactsPath(kUnittestPrivateKeyPath)}); + string signature; + SignSampleData(&signature, {GetBuildArtifactsPath(kUnittestPrivateKeyPath)}); // Check the signature itself Signatures signatures; - EXPECT_TRUE( - signatures.ParseFromArray(signature_blob.data(), signature_blob.size())); + EXPECT_TRUE(signatures.ParseFromString(signature)); EXPECT_EQ(1, signatures.signatures_size()); - const Signatures_Signature& signature = signatures.signatures(0); - EXPECT_EQ(1U, signature.version()); - const string& sig_data = signature.data(); + const Signatures::Signature& sig = signatures.signatures(0); + const string& sig_data = sig.data(); ASSERT_EQ(base::size(kDataSignature), sig_data.size()); for (size_t i = 0; i < base::size(kDataSignature); i++) { EXPECT_EQ(kDataSignature[i], static_cast<uint8_t>(sig_data[i])); @@ -132,38 +126,44 @@ TEST_F(PayloadSignerTest, SignSimpleTextTest) { } TEST_F(PayloadSignerTest, VerifyAllSignatureTest) { - brillo::Blob signature_blob; - SignSampleData(&signature_blob, + string signature; + SignSampleData(&signature, {GetBuildArtifactsPath(kUnittestPrivateKeyPath), - GetBuildArtifactsPath(kUnittestPrivateKey2Path)}); + GetBuildArtifactsPath(kUnittestPrivateKey2Path), + GetBuildArtifactsPath(kUnittestPrivateKeyRSA4096Path), + GetBuildArtifactsPath(kUnittestPrivateKeyECPath)}); // Either public key should pass the verification. - string public_key; - EXPECT_TRUE(utils::ReadFile(GetBuildArtifactsPath(kUnittestPublicKeyPath), - &public_key)); - EXPECT_TRUE(PayloadVerifier::VerifySignature( - signature_blob, public_key, padded_hash_data_)); - EXPECT_TRUE(utils::ReadFile(GetBuildArtifactsPath(kUnittestPublicKey2Path), - &public_key)); - EXPECT_TRUE(PayloadVerifier::VerifySignature( - signature_blob, public_key, padded_hash_data_)); + for (const auto& path : {kUnittestPublicKeyPath, + kUnittestPublicKey2Path, + kUnittestPublicKeyRSA4096Path, + kUnittestPublicKeyECPath}) { + string public_key; + EXPECT_TRUE(utils::ReadFile(GetBuildArtifactsPath(path), &public_key)); + auto payload_verifier = PayloadVerifier::CreateInstance(public_key); + EXPECT_TRUE(payload_verifier != nullptr); + EXPECT_TRUE(payload_verifier->VerifySignature(signature, hash_data_)); + } } TEST_F(PayloadSignerTest, VerifySignatureTest) { - brillo::Blob signature_blob; - SignSampleData(&signature_blob, - {GetBuildArtifactsPath(kUnittestPrivateKeyPath)}); + string signature; + SignSampleData(&signature, {GetBuildArtifactsPath(kUnittestPrivateKeyPath)}); string public_key; EXPECT_TRUE(utils::ReadFile(GetBuildArtifactsPath(kUnittestPublicKeyPath), &public_key)); - EXPECT_TRUE(PayloadVerifier::VerifySignature( - signature_blob, public_key, padded_hash_data_)); + auto payload_verifier = PayloadVerifier::CreateInstance(public_key); + EXPECT_TRUE(payload_verifier != nullptr); + EXPECT_TRUE(payload_verifier->VerifySignature(signature, hash_data_)); + // Passing the invalid key should fail the verification. + public_key.clear(); EXPECT_TRUE(utils::ReadFile(GetBuildArtifactsPath(kUnittestPublicKey2Path), &public_key)); - EXPECT_TRUE(PayloadVerifier::VerifySignature( - signature_blob, public_key, padded_hash_data_)); + payload_verifier = PayloadVerifier::CreateInstance(public_key); + EXPECT_TRUE(payload_verifier != nullptr); + EXPECT_FALSE(payload_verifier->VerifySignature(signature, hash_data_)); } TEST_F(PayloadSignerTest, SkipMetadataSignatureTest) { @@ -175,7 +175,7 @@ TEST_F(PayloadSignerTest, SkipMetadataSignatureTest) { uint64_t metadata_size; EXPECT_TRUE(payload.WritePayload( payload_file.path(), "/dev/null", "", &metadata_size)); - const vector<int> sizes = {256}; + const vector<size_t> sizes = {256}; brillo::Blob unsigned_payload_hash, unsigned_metadata_hash; EXPECT_TRUE(PayloadSigner::HashPayloadForSigning(payload_file.path(), sizes, diff --git a/payload_generator/squashfs_filesystem.cc b/payload_generator/squashfs_filesystem.cc index eb4fda37..6152d7d6 100644 --- a/payload_generator/squashfs_filesystem.cc +++ b/payload_generator/squashfs_filesystem.cc @@ -275,7 +275,7 @@ bool SquashfsFilesystem::Init(const string& map, auto last = std::unique(zlib_blks.begin(), zlib_blks.end()); zlib_blks.erase(last, zlib_blks.end()); - // Sanity check. Make sure zlib blocks are not overlapping. + // Make sure zlib blocks are not overlapping. auto result = std::adjacent_find( zlib_blks.begin(), zlib_blks.end(), diff --git a/payload_state.cc b/payload_state.cc index b6c054bb..1d1583b9 100644 --- a/payload_state.cc +++ b/payload_state.cc @@ -373,6 +373,8 @@ void PayloadState::UpdateFailed(ErrorCode error) { case ErrorCode::kInternalLibCurlError: case ErrorCode::kUnresolvedHostError: case ErrorCode::kUnresolvedHostRecovered: + case ErrorCode::kNotEnoughSpace: + case ErrorCode::kDeviceCorrupted: case ErrorCode::kPackageExcludedFromUpdate: LOG(INFO) << "Not incrementing URL index or failure count for this error"; break; @@ -1062,7 +1064,7 @@ void PayloadState::LoadUpdateTimestampStart() { stored_time = Time::FromInternalValue(stored_value); } - // Sanity check: If the time read from disk is in the future + // Validation check: If the time read from disk is in the future // (modulo some slack to account for possible NTP drift // adjustments), something is fishy and we should report and // reset. @@ -1109,7 +1111,7 @@ void PayloadState::LoadUpdateDurationUptime() { stored_delta = TimeDelta::FromInternalValue(stored_value); } - // Sanity-check: Uptime can never be greater than the wall-clock + // Validation check: Uptime can never be greater than the wall-clock // difference (modulo some slack). If it is, report and reset // to the wall-clock difference. TimeDelta diff = GetUpdateDuration() - stored_delta; diff --git a/scripts/brillo_update_payload b/scripts/brillo_update_payload index c88709ce..3bc87bd6 100755 --- a/scripts/brillo_update_payload +++ b/scripts/brillo_update_payload @@ -28,12 +28,16 @@ # check verify a payload using paycheck (static testing) # # Generate command arguments: -# --payload generated unsigned payload output file -# --source_image if defined, generate a delta payload from the specified -# image to the target_image -# --target_image the target image that should be sent to clients -# --metadata_size_file if defined, generate a file containing the size of the -# payload metadata in bytes to the specified file +# --payload generated unsigned payload output file +# --source_image if defined, generate a delta payload from the +# specified image to the target_image +# --target_image the target image that should be sent to clients +# --metadata_size_file if defined, generate a file containing the size +# of the ayload metadata in bytes to the specified +# file +# --disable_fec_computation Disable the on device fec data computation for +# incremental update. This feature is enabled by +# default # # Hash command arguments: # --unsigned_payload the input unsigned payload to generate the hash from @@ -182,6 +186,16 @@ if [[ "${COMMAND}" == "generate" ]]; then "Optional: The maximum unix timestamp of the OS allowed to apply this \ payload, should be set to a number higher than the build timestamp of the \ system running on the device, 0 if not specified." + DEFINE_string partition_timestamps "" \ + "Optional: Per-partition maximum unix timestamp of the OS allowed to \ +apply this payload. Should be a comma separated key value pairs. e.x.\ +system:1234,vendor:456" + DEFINE_string disable_fec_computation "" \ + "Optional: Disables the on device fec data computation for incremental \ +update. This feature is enabled by default." + DEFINE_string is_partial_update "" \ + "Optional: True if the payload is for partial update. i.e. it only updates \ +a subset of partitions on device." fi if [[ "${COMMAND}" == "hash" || "${COMMAND}" == "sign" ]]; then DEFINE_string unsigned_payload "" "Path to the input unsigned payload." @@ -248,6 +262,9 @@ declare -A DST_PARTITIONS_MAP # List of partition names in order. declare -a PARTITIONS_ORDER +# A list of PIDs of the extract_image workers. +EXTRACT_IMAGE_PIDS=() + # A list of temporary files to remove during cleanup. CLEANUP_FILES=() @@ -271,7 +288,7 @@ read_option_uint() { local option_key="$2" local default_value="${3:-}" local value - if value=$(look "${option_key}=" "${file_txt}" | tail -n 1); then + if value=$(grep "^${option_key}=" "${file_txt}" | tail -n 1); then if value=$(echo "${value}" | cut -f 2- -d "=" | grep -E "^[0-9]+$"); then echo "${value}" return @@ -324,6 +341,25 @@ cleanup_on_exit() { trap cleanup_on_error INT TERM ERR trap cleanup_on_exit EXIT +# extract_file <zip_file> <entry_name> <destination> +# +# Extracts |entry_name| from |zip_file| to |destination|. +extract_file() { + local zip_file="$1" + local entry_name="$2" + local destination="$3" + + # unzip -p won't report error upon ENOSPC. Therefore, create a temp directory + # as the destination of the unzip, and move the file to the intended + # destination. + local output_directory=$( + mktemp --directory --tmpdir="${FLAGS_work_dir}" "TEMP.XXXXXX") + unzip "${zip_file}" "${entry_name}" -d "${output_directory}" || + { rm -rf "${output_directory}"; die "Failed to extract ${entry_name}"; } + + mv "${output_directory}/${entry_name}" "${destination}" + rm -rf "${output_directory}" +} # extract_image <image> <partitions_array> [partitions_order] # @@ -414,7 +450,7 @@ extract_partition_brillo() { fi done [[ -n "${path_in_zip}" ]] || die "Failed to find ${part}.img" - unzip -p "${image}" "${path_in_zip}/${part}.img" >"${part_file}" + extract_file "${image}" "${path_in_zip}/${part}.img" "${part_file}" # If the partition is stored as an Android sparse image file, we need to # convert them to a raw image for the update. @@ -428,8 +464,9 @@ extract_partition_brillo() { fi # Extract the .map file (if one is available). - unzip -p "${image}" "${path_in_zip}/${part}.map" >"${part_map_file}" \ - 2>/dev/null || true + if unzip -l "${image}" "${path_in_zip}/${part}.map" > /dev/null; then + extract_file "${image}" "${path_in_zip}/${part}.map" "${part_map_file}" + fi # delta_generator only supports images multiple of 4 KiB. For target images # we pad the data with zeros if needed, but for source images we truncate @@ -463,7 +500,8 @@ extract_image_brillo() { local ab_partitions_list ab_partitions_list=$(create_tempfile "ab_partitions_list.XXXXXX") CLEANUP_FILES+=("${ab_partitions_list}") - if unzip -p "${image}" "META/ab_partitions.txt" >"${ab_partitions_list}"; then + if unzip -l "${image}" "META/ab_partitions.txt" > /dev/null; then + extract_file "${image}" "META/ab_partitions.txt" "${ab_partitions_list}" if grep -v -E '^[a-zA-Z0-9_-]*$' "${ab_partitions_list}" >&2; then die "Invalid partition names found in the partition list." fi @@ -488,8 +526,9 @@ extract_image_brillo() { # Source image local ue_config=$(create_tempfile "ue_config.XXXXXX") CLEANUP_FILES+=("${ue_config}") - if ! unzip -p "${image}" "META/update_engine_config.txt" \ - >"${ue_config}"; then + if unzip -l "${image}" "META/update_engine_config.txt" > /dev/null; then + extract_file "${image}" "META/update_engine_config.txt" "${ue_config}" + else warn "No update_engine_config.txt found. Assuming pre-release image, \ using payload minor version 2" fi @@ -510,14 +549,16 @@ Disabling deltas for this source version." # Target image local postinstall_config=$(create_tempfile "postinstall_config.XXXXXX") CLEANUP_FILES+=("${postinstall_config}") - if unzip -p "${image}" "META/postinstall_config.txt" \ - >"${postinstall_config}"; then + if unzip -l "${image}" "META/postinstall_config.txt" > /dev/null; then + extract_file "${image}" "META/postinstall_config.txt" \ + "${postinstall_config}" POSTINSTALL_CONFIG_FILE="${postinstall_config}" fi local dynamic_partitions_info=$(create_tempfile "dynamic_partitions_info.XXXXXX") CLEANUP_FILES+=("${dynamic_partitions_info}") - if unzip -p "${image}" "META/dynamic_partitions_info.txt" \ - >"${dynamic_partitions_info}"; then + if unzip -l "${image}" "META/dynamic_partitions_info.txt" > /dev/null; then + extract_file "${image}" "META/dynamic_partitions_info.txt" \ + "${dynamic_partitions_info}" DYNAMIC_PARTITION_INFO_FILE="${dynamic_partitions_info}" fi fi @@ -530,6 +571,7 @@ Disabling deltas for this source version." # Extract partitions in background. extract_partition_brillo "${image}" "${partitions_array}" "${part}" \ "${part_file}" "${part_map_file}" & + EXTRACT_IMAGE_PIDS+=("$!") eval "${partitions_array}[\"${part}\"]=\"${part_file}\"" eval "${partitions_array}_MAP[\"${part}\"]=\"${part_map_file}\"" done @@ -559,8 +601,12 @@ extract_payload_images() { extract_image "${FLAGS_source_image}" SRC_PARTITIONS fi extract_image "${FLAGS_target_image}" DST_PARTITIONS PARTITIONS_ORDER - # Wait for all subprocesses. - wait + # Wait for all subprocesses to finish. Not using `wait` since it doesn't die + # on non-zero subprocess exit code. Not using `wait ${EXTRACT_IMAGE_PIDS[@]}` + # as it gives the status of the last process it has waited for. + for pid in ${EXTRACT_IMAGE_PIDS[@]}; do + wait ${pid} + done cleanup_partition_array SRC_PARTITIONS cleanup_partition_array SRC_PARTITIONS_MAP cleanup_partition_array DST_PARTITIONS @@ -615,17 +661,33 @@ cmd_generate() { --new_mapfiles="${new_mapfiles}" ) + if [[ "${FLAGS_is_partial_update}" == "true" ]]; then + GENERATOR_ARGS+=( --is_partial_update="true" ) + # Need at least minor version 7 for partial update, so generate with minor + # version 7 if we don't have a source image. Let the delta_generator to fail + # the other incompatiable minor versions. + if [[ -z "${FORCE_MINOR_VERSION}" ]]; then + FORCE_MINOR_VERSION="7" + fi + fi + if [[ "${payload_type}" == "delta" ]]; then # Source image args: GENERATOR_ARGS+=( --old_partitions="${old_partitions}" --old_mapfiles="${old_mapfiles}" ) - if [[ -n "${FORCE_MINOR_VERSION}" ]]; then - GENERATOR_ARGS+=( --minor_version="${FORCE_MINOR_VERSION}" ) + if [[ -n "${FLAGS_disable_fec_computation}" ]]; then + GENERATOR_ARGS+=( + --disable_fec_computation="${FLAGS_disable_fec_computation}" ) fi fi + # minor version is set only for delta or partial payload. + if [[ -n "${FORCE_MINOR_VERSION}" ]]; then + GENERATOR_ARGS+=( --minor_version="${FORCE_MINOR_VERSION}" ) + fi + if [[ -n "${FORCE_MAJOR_VERSION}" ]]; then GENERATOR_ARGS+=( --major_version="${FORCE_MAJOR_VERSION}" ) fi @@ -638,6 +700,10 @@ cmd_generate() { GENERATOR_ARGS+=( --max_timestamp="${FLAGS_max_timestamp}" ) fi + if [[ -n "${FLAGS_partition_timestamps}" ]]; then + GENERATOR_ARGS+=( --partition_timestamps="${FLAGS_partition_timestamps}" ) + fi + if [[ -n "${POSTINSTALL_CONFIG_FILE}" ]]; then GENERATOR_ARGS+=( --new_postinstall_config_file="${POSTINSTALL_CONFIG_FILE}" @@ -838,8 +904,8 @@ cmd_check() { check_update_payload ${PAYCHECK_ARGS[@]} --check } -# Sanity check that the real generator exists: -GENERATOR="$(which delta_generator || true)" +# Check that the real generator exists: +[[ -x "${GENERATOR}" ]] || GENERATOR="$(which delta_generator || true)" [[ -x "${GENERATOR}" ]] || die "can't find delta_generator" case "$COMMAND" in diff --git a/scripts/payload_info.py b/scripts/payload_info.py index 965bb76f..7625ee8e 100755 --- a/scripts/payload_info.py +++ b/scripts/payload_info.py @@ -74,7 +74,9 @@ class PayloadCommand(object): for partition in manifest.partitions: DisplayValue(' Number of "%s" ops' % partition.partition_name, len(partition.operations)) - + for partition in manifest.partitions: + DisplayValue("Timestamp for " + + partition.partition_name, partition.version) DisplayValue('Block size', manifest.block_size) DisplayValue('Minor version', manifest.minor_version) diff --git a/scripts/update_device.py b/scripts/update_device.py index f970bd3e..1cd4b6ae 100755 --- a/scripts/update_device.py +++ b/scripts/update_device.py @@ -20,12 +20,14 @@ from __future__ import absolute_import import argparse +import binascii import hashlib import logging import os import socket import subprocess import sys +import struct import threading import xml.etree.ElementTree import zipfile @@ -87,17 +89,49 @@ class AndroidOTAPackage(object): # Android OTA package file paths. OTA_PAYLOAD_BIN = 'payload.bin' OTA_PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt' + SECONDARY_OTA_PAYLOAD_BIN = 'secondary/payload.bin' + SECONDARY_OTA_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt' + PAYLOAD_MAGIC_HEADER = b'CrAU' - def __init__(self, otafilename): + def __init__(self, otafilename, secondary_payload=False): self.otafilename = otafilename otazip = zipfile.ZipFile(otafilename, 'r') - payload_info = otazip.getinfo(self.OTA_PAYLOAD_BIN) - self.offset = payload_info.header_offset - self.offset += zipfile.sizeFileHeader - self.offset += len(payload_info.extra) + len(payload_info.filename) - self.size = payload_info.file_size - self.properties = otazip.read(self.OTA_PAYLOAD_PROPERTIES_TXT) + payload_entry = (self.SECONDARY_OTA_PAYLOAD_BIN if secondary_payload else + self.OTA_PAYLOAD_BIN) + payload_info = otazip.getinfo(payload_entry) + + if payload_info.compress_type != 0: + logging.error( + "Expected layload to be uncompressed, got compression method %d", + payload_info.compress_type) + # Don't use len(payload_info.extra). Because that returns size of extra + # fields in central directory. We need to look at local file directory, + # as these two might have different sizes. + with open(otafilename, "rb") as fp: + fp.seek(payload_info.header_offset) + data = fp.read(zipfile.sizeFileHeader) + fheader = struct.unpack(zipfile.structFileHeader, data) + # Last two fields of local file header are filename length and + # extra length + filename_len = fheader[-2] + extra_len = fheader[-1] + self.offset = payload_info.header_offset + self.offset += zipfile.sizeFileHeader + self.offset += filename_len + extra_len + self.size = payload_info.file_size + fp.seek(self.offset) + payload_header = fp.read(4) + if payload_header != self.PAYLOAD_MAGIC_HEADER: + logging.warning( + "Invalid header, expeted %s, got %s." + "Either the offset is not correct, or payload is corrupted", + binascii.hexlify(self.PAYLOAD_MAGIC_HEADER), + payload_header) + + property_entry = (self.SECONDARY_OTA_PAYLOAD_PROPERTIES_TXT if + secondary_payload else self.OTA_PAYLOAD_PROPERTIES_TXT) + self.properties = otazip.read(property_entry) class UpdateHandler(BaseHTTPServer.BaseHTTPRequestHandler): @@ -280,9 +314,9 @@ def StartServer(ota_filename, serving_range): return t -def AndroidUpdateCommand(ota_filename, payload_url, extra_headers): +def AndroidUpdateCommand(ota_filename, secondary, payload_url, extra_headers): """Return the command to run to start the update in the Android device.""" - ota = AndroidOTAPackage(ota_filename) + ota = AndroidOTAPackage(ota_filename, secondary) headers = ota.properties headers += 'USER_AGENT=Dalvik (something, something)\n' headers += 'NETWORK_ID=0\n' @@ -365,6 +399,8 @@ def main(): help='Override the public key used to verify payload.') parser.add_argument('--extra-headers', type=str, default='', help='Extra headers to pass to the device.') + parser.add_argument('--secondary', action='store_true', + help='Update with the secondary payload in the package.') args = parser.parse_args() logging.basicConfig( level=logging.WARNING if args.no_verbose else logging.INFO) @@ -400,7 +436,7 @@ def main(): # command. payload_url = 'http://127.0.0.1:%d/payload' % DEVICE_PORT if use_omaha and zipfile.is_zipfile(args.otafile): - ota = AndroidOTAPackage(args.otafile) + ota = AndroidOTAPackage(args.otafile, args.secondary) serving_range = (ota.offset, ota.size) else: serving_range = (0, os.stat(args.otafile).st_size) @@ -428,8 +464,8 @@ def main(): update_cmd = \ OmahaUpdateCommand('http://127.0.0.1:%d/update' % DEVICE_PORT) else: - update_cmd = \ - AndroidUpdateCommand(args.otafile, payload_url, args.extra_headers) + update_cmd = AndroidUpdateCommand(args.otafile, args.secondary, + payload_url, args.extra_headers) cmds.append(['shell', 'su', '0'] + update_cmd) for cmd in cmds: diff --git a/scripts/update_payload/payload.py b/scripts/update_payload/payload.py index ea5ed308..78b8e2ca 100644 --- a/scripts/update_payload/payload.py +++ b/scripts/update_payload/payload.py @@ -20,7 +20,9 @@ from __future__ import absolute_import from __future__ import print_function import hashlib +import io import struct +import zipfile from update_payload import applier from update_payload import checker @@ -119,6 +121,10 @@ class Payload(object): payload_file: update payload file object open for reading payload_file_offset: the offset of the actual payload """ + if zipfile.is_zipfile(payload_file): + with zipfile.ZipFile(payload_file) as zfp: + with zfp.open("payload.bin") as payload_fp: + payload_file = io.BytesIO(payload_fp.read()) self.payload_file = payload_file self.payload_file_offset = payload_file_offset self.manifest_hasher = None diff --git a/scripts/update_payload/update_metadata_pb2.py b/scripts/update_payload/update_metadata_pb2.py index 907cc18a..841cd22b 100644 --- a/scripts/update_payload/update_metadata_pb2.py +++ b/scripts/update_payload/update_metadata_pb2.py @@ -2,8 +2,6 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # source: update_metadata.proto -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection @@ -19,8 +17,8 @@ DESCRIPTOR = _descriptor.FileDescriptor( name='update_metadata.proto', package='chromeos_update_engine', syntax='proto2', - serialized_options=_b('H\003'), - serialized_pb=_b('\n\x15update_metadata.proto\x12\x16\x63hromeos_update_engine\"1\n\x06\x45xtent\x12\x13\n\x0bstart_block\x18\x01 \x01(\x04\x12\x12\n\nnum_blocks\x18\x02 \x01(\x04\"z\n\nSignatures\x12@\n\nsignatures\x18\x01 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x1a*\n\tSignature\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"+\n\rPartitionInfo\x12\x0c\n\x04size\x18\x01 \x01(\x04\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\"w\n\tImageInfo\x12\r\n\x05\x62oard\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\t\x12\x0f\n\x07version\x18\x04 \x01(\t\x12\x15\n\rbuild_channel\x18\x05 \x01(\t\x12\x15\n\rbuild_version\x18\x06 \x01(\t\"\xee\x03\n\x10InstallOperation\x12;\n\x04type\x18\x01 \x02(\x0e\x32-.chromeos_update_engine.InstallOperation.Type\x12\x13\n\x0b\x64\x61ta_offset\x18\x02 \x01(\x04\x12\x13\n\x0b\x64\x61ta_length\x18\x03 \x01(\x04\x12\x33\n\x0bsrc_extents\x18\x04 \x03(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\nsrc_length\x18\x05 \x01(\x04\x12\x33\n\x0b\x64st_extents\x18\x06 \x03(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\ndst_length\x18\x07 \x01(\x04\x12\x18\n\x10\x64\x61ta_sha256_hash\x18\x08 \x01(\x0c\x12\x17\n\x0fsrc_sha256_hash\x18\t \x01(\x0c\"\xad\x01\n\x04Type\x12\x0b\n\x07REPLACE\x10\x00\x12\x0e\n\nREPLACE_BZ\x10\x01\x12\x0c\n\x04MOVE\x10\x02\x1a\x02\x08\x01\x12\x0e\n\x06\x42SDIFF\x10\x03\x1a\x02\x08\x01\x12\x0f\n\x0bSOURCE_COPY\x10\x04\x12\x11\n\rSOURCE_BSDIFF\x10\x05\x12\x0e\n\nREPLACE_XZ\x10\x08\x12\x08\n\x04ZERO\x10\x06\x12\x0b\n\x07\x44ISCARD\x10\x07\x12\x11\n\rBROTLI_BSDIFF\x10\n\x12\x0c\n\x08PUFFDIFF\x10\t\"\xd7\x05\n\x0fPartitionUpdate\x12\x16\n\x0epartition_name\x18\x01 \x02(\t\x12\x17\n\x0frun_postinstall\x18\x02 \x01(\x08\x12\x18\n\x10postinstall_path\x18\x03 \x01(\t\x12\x17\n\x0f\x66ilesystem_type\x18\x04 \x01(\t\x12M\n\x17new_partition_signature\x18\x05 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x12\x41\n\x12old_partition_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12\x41\n\x12new_partition_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12<\n\noperations\x18\x08 \x03(\x0b\x32(.chromeos_update_engine.InstallOperation\x12\x1c\n\x14postinstall_optional\x18\t \x01(\x08\x12=\n\x15hash_tree_data_extent\x18\n \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x38\n\x10hash_tree_extent\x18\x0b \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x1b\n\x13hash_tree_algorithm\x18\x0c \x01(\t\x12\x16\n\x0ehash_tree_salt\x18\r \x01(\x0c\x12\x37\n\x0f\x66\x65\x63_data_extent\x18\x0e \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x32\n\nfec_extent\x18\x0f \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x14\n\tfec_roots\x18\x10 \x01(\r:\x01\x32\"L\n\x15\x44ynamicPartitionGroup\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x0c\n\x04size\x18\x02 \x01(\x04\x12\x17\n\x0fpartition_names\x18\x03 \x03(\t\"Y\n\x18\x44ynamicPartitionMetadata\x12=\n\x06groups\x18\x01 \x03(\x0b\x32-.chromeos_update_engine.DynamicPartitionGroup\"\xc9\x06\n\x14\x44\x65ltaArchiveManifest\x12H\n\x12install_operations\x18\x01 \x03(\x0b\x32(.chromeos_update_engine.InstallOperationB\x02\x18\x01\x12O\n\x19kernel_install_operations\x18\x02 \x03(\x0b\x32(.chromeos_update_engine.InstallOperationB\x02\x18\x01\x12\x18\n\nblock_size\x18\x03 \x01(\r:\x04\x34\x30\x39\x36\x12\x19\n\x11signatures_offset\x18\x04 \x01(\x04\x12\x17\n\x0fsignatures_size\x18\x05 \x01(\x04\x12\x42\n\x0fold_kernel_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fnew_kernel_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fold_rootfs_info\x18\x08 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fnew_rootfs_info\x18\t \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x39\n\x0eold_image_info\x18\n \x01(\x0b\x32!.chromeos_update_engine.ImageInfo\x12\x39\n\x0enew_image_info\x18\x0b \x01(\x0b\x32!.chromeos_update_engine.ImageInfo\x12\x18\n\rminor_version\x18\x0c \x01(\r:\x01\x30\x12;\n\npartitions\x18\r \x03(\x0b\x32\'.chromeos_update_engine.PartitionUpdate\x12\x15\n\rmax_timestamp\x18\x0e \x01(\x03\x12T\n\x1a\x64ynamic_partition_metadata\x18\x0f \x01(\x0b\x32\x30.chromeos_update_engine.DynamicPartitionMetadataB\x02H\x03') + serialized_options=b'H\003', + serialized_pb=b'\n\x15update_metadata.proto\x12\x16\x63hromeos_update_engine\"1\n\x06\x45xtent\x12\x13\n\x0bstart_block\x18\x01 \x01(\x04\x12\x12\n\nnum_blocks\x18\x02 \x01(\x04\"\x9f\x01\n\nSignatures\x12@\n\nsignatures\x18\x01 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x1aO\n\tSignature\x12\x13\n\x07version\x18\x01 \x01(\rB\x02\x18\x01\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x1f\n\x17unpadded_signature_size\x18\x03 \x01(\x07\"+\n\rPartitionInfo\x12\x0c\n\x04size\x18\x01 \x01(\x04\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\"w\n\tImageInfo\x12\r\n\x05\x62oard\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\t\x12\x0f\n\x07version\x18\x04 \x01(\t\x12\x15\n\rbuild_channel\x18\x05 \x01(\t\x12\x15\n\rbuild_version\x18\x06 \x01(\t\"\xee\x03\n\x10InstallOperation\x12;\n\x04type\x18\x01 \x02(\x0e\x32-.chromeos_update_engine.InstallOperation.Type\x12\x13\n\x0b\x64\x61ta_offset\x18\x02 \x01(\x04\x12\x13\n\x0b\x64\x61ta_length\x18\x03 \x01(\x04\x12\x33\n\x0bsrc_extents\x18\x04 \x03(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\nsrc_length\x18\x05 \x01(\x04\x12\x33\n\x0b\x64st_extents\x18\x06 \x03(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\ndst_length\x18\x07 \x01(\x04\x12\x18\n\x10\x64\x61ta_sha256_hash\x18\x08 \x01(\x0c\x12\x17\n\x0fsrc_sha256_hash\x18\t \x01(\x0c\"\xad\x01\n\x04Type\x12\x0b\n\x07REPLACE\x10\x00\x12\x0e\n\nREPLACE_BZ\x10\x01\x12\x0c\n\x04MOVE\x10\x02\x1a\x02\x08\x01\x12\x0e\n\x06\x42SDIFF\x10\x03\x1a\x02\x08\x01\x12\x0f\n\x0bSOURCE_COPY\x10\x04\x12\x11\n\rSOURCE_BSDIFF\x10\x05\x12\x0e\n\nREPLACE_XZ\x10\x08\x12\x08\n\x04ZERO\x10\x06\x12\x0b\n\x07\x44ISCARD\x10\x07\x12\x11\n\rBROTLI_BSDIFF\x10\n\x12\x0c\n\x08PUFFDIFF\x10\t\"\xe8\x05\n\x0fPartitionUpdate\x12\x16\n\x0epartition_name\x18\x01 \x02(\t\x12\x17\n\x0frun_postinstall\x18\x02 \x01(\x08\x12\x18\n\x10postinstall_path\x18\x03 \x01(\t\x12\x17\n\x0f\x66ilesystem_type\x18\x04 \x01(\t\x12M\n\x17new_partition_signature\x18\x05 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x12\x41\n\x12old_partition_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12\x41\n\x12new_partition_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12<\n\noperations\x18\x08 \x03(\x0b\x32(.chromeos_update_engine.InstallOperation\x12\x1c\n\x14postinstall_optional\x18\t \x01(\x08\x12=\n\x15hash_tree_data_extent\x18\n \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x38\n\x10hash_tree_extent\x18\x0b \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x1b\n\x13hash_tree_algorithm\x18\x0c \x01(\t\x12\x16\n\x0ehash_tree_salt\x18\r \x01(\x0c\x12\x37\n\x0f\x66\x65\x63_data_extent\x18\x0e \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x32\n\nfec_extent\x18\x0f \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x14\n\tfec_roots\x18\x10 \x01(\r:\x01\x32\x12\x0f\n\x07version\x18\x11 \x01(\t\"L\n\x15\x44ynamicPartitionGroup\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x0c\n\x04size\x18\x02 \x01(\x04\x12\x17\n\x0fpartition_names\x18\x03 \x03(\t\"s\n\x18\x44ynamicPartitionMetadata\x12=\n\x06groups\x18\x01 \x03(\x0b\x32-.chromeos_update_engine.DynamicPartitionGroup\x12\x18\n\x10snapshot_enabled\x18\x02 \x01(\x08\"\xe1\x06\n\x14\x44\x65ltaArchiveManifest\x12H\n\x12install_operations\x18\x01 \x03(\x0b\x32(.chromeos_update_engine.InstallOperationB\x02\x18\x01\x12O\n\x19kernel_install_operations\x18\x02 \x03(\x0b\x32(.chromeos_update_engine.InstallOperationB\x02\x18\x01\x12\x18\n\nblock_size\x18\x03 \x01(\r:\x04\x34\x30\x39\x36\x12\x19\n\x11signatures_offset\x18\x04 \x01(\x04\x12\x17\n\x0fsignatures_size\x18\x05 \x01(\x04\x12\x42\n\x0fold_kernel_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fnew_kernel_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fold_rootfs_info\x18\x08 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fnew_rootfs_info\x18\t \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x39\n\x0eold_image_info\x18\n \x01(\x0b\x32!.chromeos_update_engine.ImageInfo\x12\x39\n\x0enew_image_info\x18\x0b \x01(\x0b\x32!.chromeos_update_engine.ImageInfo\x12\x18\n\rminor_version\x18\x0c \x01(\r:\x01\x30\x12;\n\npartitions\x18\r \x03(\x0b\x32\'.chromeos_update_engine.PartitionUpdate\x12\x15\n\rmax_timestamp\x18\x0e \x01(\x03\x12T\n\x1a\x64ynamic_partition_metadata\x18\x0f \x01(\x0b\x32\x30.chromeos_update_engine.DynamicPartitionMetadata\x12\x16\n\x0epartial_update\x18\x10 \x01(\x08\x42\x02H\x03' ) @@ -41,11 +39,11 @@ _INSTALLOPERATION_TYPE = _descriptor.EnumDescriptor( type=None), _descriptor.EnumValueDescriptor( name='MOVE', index=2, number=2, - serialized_options=_b('\010\001'), + serialized_options=b'\010\001', type=None), _descriptor.EnumValueDescriptor( name='BSDIFF', index=3, number=3, - serialized_options=_b('\010\001'), + serialized_options=b'\010\001', type=None), _descriptor.EnumValueDescriptor( name='SOURCE_COPY', index=4, number=4, @@ -78,8 +76,8 @@ _INSTALLOPERATION_TYPE = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=712, - serialized_end=885, + serialized_start=750, + serialized_end=923, ) _sym_db.RegisterEnumDescriptor(_INSTALLOPERATION_TYPE) @@ -135,11 +133,18 @@ _SIGNATURES_SIGNATURE = _descriptor.Descriptor( has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=b'\030\001', file=DESCRIPTOR), _descriptor.FieldDescriptor( name='data', full_name='chromeos_update_engine.Signatures.Signature.data', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='unpadded_signature_size', full_name='chromeos_update_engine.Signatures.Signature.unpadded_signature_size', index=2, + number=3, type=7, cpp_type=3, label=1, + has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -155,8 +160,8 @@ _SIGNATURES_SIGNATURE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=180, - serialized_end=222, + serialized_start=181, + serialized_end=260, ) _SIGNATURES = _descriptor.Descriptor( @@ -185,8 +190,8 @@ _SIGNATURES = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=100, - serialized_end=222, + serialized_start=101, + serialized_end=260, ) @@ -207,7 +212,7 @@ _PARTITIONINFO = _descriptor.Descriptor( _descriptor.FieldDescriptor( name='hash', full_name='chromeos_update_engine.PartitionInfo.hash', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -223,8 +228,8 @@ _PARTITIONINFO = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=224, - serialized_end=267, + serialized_start=262, + serialized_end=305, ) @@ -238,42 +243,42 @@ _IMAGEINFO = _descriptor.Descriptor( _descriptor.FieldDescriptor( name='board', full_name='chromeos_update_engine.ImageInfo.board', index=0, number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='key', full_name='chromeos_update_engine.ImageInfo.key', index=1, number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='channel', full_name='chromeos_update_engine.ImageInfo.channel', index=2, number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='version', full_name='chromeos_update_engine.ImageInfo.version', index=3, number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='build_channel', full_name='chromeos_update_engine.ImageInfo.build_channel', index=4, number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='build_version', full_name='chromeos_update_engine.ImageInfo.build_version', index=5, number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -289,8 +294,8 @@ _IMAGEINFO = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=269, - serialized_end=388, + serialized_start=307, + serialized_end=426, ) @@ -353,14 +358,14 @@ _INSTALLOPERATION = _descriptor.Descriptor( _descriptor.FieldDescriptor( name='data_sha256_hash', full_name='chromeos_update_engine.InstallOperation.data_sha256_hash', index=7, number=8, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='src_sha256_hash', full_name='chromeos_update_engine.InstallOperation.src_sha256_hash', index=8, number=9, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -377,8 +382,8 @@ _INSTALLOPERATION = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=391, - serialized_end=885, + serialized_start=429, + serialized_end=923, ) @@ -392,7 +397,7 @@ _PARTITIONUPDATE = _descriptor.Descriptor( _descriptor.FieldDescriptor( name='partition_name', full_name='chromeos_update_engine.PartitionUpdate.partition_name', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -406,14 +411,14 @@ _PARTITIONUPDATE = _descriptor.Descriptor( _descriptor.FieldDescriptor( name='postinstall_path', full_name='chromeos_update_engine.PartitionUpdate.postinstall_path', index=2, number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='filesystem_type', full_name='chromeos_update_engine.PartitionUpdate.filesystem_type', index=3, number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -469,14 +474,14 @@ _PARTITIONUPDATE = _descriptor.Descriptor( _descriptor.FieldDescriptor( name='hash_tree_algorithm', full_name='chromeos_update_engine.PartitionUpdate.hash_tree_algorithm', index=11, number=12, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='hash_tree_salt', full_name='chromeos_update_engine.PartitionUpdate.hash_tree_salt', index=12, number=13, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -501,6 +506,13 @@ _PARTITIONUPDATE = _descriptor.Descriptor( message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='version', full_name='chromeos_update_engine.PartitionUpdate.version', index=16, + number=17, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -513,8 +525,8 @@ _PARTITIONUPDATE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=888, - serialized_end=1615, + serialized_start=926, + serialized_end=1670, ) @@ -528,7 +540,7 @@ _DYNAMICPARTITIONGROUP = _descriptor.Descriptor( _descriptor.FieldDescriptor( name='name', full_name='chromeos_update_engine.DynamicPartitionGroup.name', index=0, number=1, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -558,8 +570,8 @@ _DYNAMICPARTITIONGROUP = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1617, - serialized_end=1693, + serialized_start=1672, + serialized_end=1748, ) @@ -577,6 +589,13 @@ _DYNAMICPARTITIONMETADATA = _descriptor.Descriptor( message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='snapshot_enabled', full_name='chromeos_update_engine.DynamicPartitionMetadata.snapshot_enabled', index=1, + number=2, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -589,8 +608,8 @@ _DYNAMICPARTITIONMETADATA = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1695, - serialized_end=1784, + serialized_start=1750, + serialized_end=1865, ) @@ -607,14 +626,14 @@ _DELTAARCHIVEMANIFEST = _descriptor.Descriptor( has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=_b('\030\001'), file=DESCRIPTOR), + serialized_options=b'\030\001', file=DESCRIPTOR), _descriptor.FieldDescriptor( name='kernel_install_operations', full_name='chromeos_update_engine.DeltaArchiveManifest.kernel_install_operations', index=1, number=2, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=_b('\030\001'), file=DESCRIPTOR), + serialized_options=b'\030\001', file=DESCRIPTOR), _descriptor.FieldDescriptor( name='block_size', full_name='chromeos_update_engine.DeltaArchiveManifest.block_size', index=2, number=3, type=13, cpp_type=3, label=1, @@ -642,28 +661,28 @@ _DELTAARCHIVEMANIFEST = _descriptor.Descriptor( has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=_b('\030\001'), file=DESCRIPTOR), + serialized_options=b'\030\001', file=DESCRIPTOR), _descriptor.FieldDescriptor( name='new_kernel_info', full_name='chromeos_update_engine.DeltaArchiveManifest.new_kernel_info', index=6, number=7, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=_b('\030\001'), file=DESCRIPTOR), + serialized_options=b'\030\001', file=DESCRIPTOR), _descriptor.FieldDescriptor( name='old_rootfs_info', full_name='chromeos_update_engine.DeltaArchiveManifest.old_rootfs_info', index=7, number=8, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=_b('\030\001'), file=DESCRIPTOR), + serialized_options=b'\030\001', file=DESCRIPTOR), _descriptor.FieldDescriptor( name='new_rootfs_info', full_name='chromeos_update_engine.DeltaArchiveManifest.new_rootfs_info', index=8, number=9, type=11, cpp_type=10, label=1, has_default_value=False, default_value=None, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=_b('\030\001'), file=DESCRIPTOR), + serialized_options=b'\030\001', file=DESCRIPTOR), _descriptor.FieldDescriptor( name='old_image_info', full_name='chromeos_update_engine.DeltaArchiveManifest.old_image_info', index=9, number=10, type=11, cpp_type=10, label=1, @@ -706,6 +725,13 @@ _DELTAARCHIVEMANIFEST = _descriptor.Descriptor( message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='partial_update', full_name='chromeos_update_engine.DeltaArchiveManifest.partial_update', index=15, + number=16, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -718,8 +744,8 @@ _DELTAARCHIVEMANIFEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1787, - serialized_end=2628, + serialized_start=1868, + serialized_end=2733, ) _SIGNATURES_SIGNATURE.containing_type = _SIGNATURES @@ -758,79 +784,80 @@ DESCRIPTOR.message_types_by_name['DynamicPartitionMetadata'] = _DYNAMICPARTITION DESCRIPTOR.message_types_by_name['DeltaArchiveManifest'] = _DELTAARCHIVEMANIFEST _sym_db.RegisterFileDescriptor(DESCRIPTOR) -Extent = _reflection.GeneratedProtocolMessageType('Extent', (_message.Message,), dict( - DESCRIPTOR = _EXTENT, - __module__ = 'update_metadata_pb2' +Extent = _reflection.GeneratedProtocolMessageType('Extent', (_message.Message,), { + 'DESCRIPTOR' : _EXTENT, + '__module__' : 'update_metadata_pb2' # @@protoc_insertion_point(class_scope:chromeos_update_engine.Extent) - )) + }) _sym_db.RegisterMessage(Extent) -Signatures = _reflection.GeneratedProtocolMessageType('Signatures', (_message.Message,), dict( +Signatures = _reflection.GeneratedProtocolMessageType('Signatures', (_message.Message,), { - Signature = _reflection.GeneratedProtocolMessageType('Signature', (_message.Message,), dict( - DESCRIPTOR = _SIGNATURES_SIGNATURE, - __module__ = 'update_metadata_pb2' + 'Signature' : _reflection.GeneratedProtocolMessageType('Signature', (_message.Message,), { + 'DESCRIPTOR' : _SIGNATURES_SIGNATURE, + '__module__' : 'update_metadata_pb2' # @@protoc_insertion_point(class_scope:chromeos_update_engine.Signatures.Signature) - )) + }) , - DESCRIPTOR = _SIGNATURES, - __module__ = 'update_metadata_pb2' + 'DESCRIPTOR' : _SIGNATURES, + '__module__' : 'update_metadata_pb2' # @@protoc_insertion_point(class_scope:chromeos_update_engine.Signatures) - )) + }) _sym_db.RegisterMessage(Signatures) _sym_db.RegisterMessage(Signatures.Signature) -PartitionInfo = _reflection.GeneratedProtocolMessageType('PartitionInfo', (_message.Message,), dict( - DESCRIPTOR = _PARTITIONINFO, - __module__ = 'update_metadata_pb2' +PartitionInfo = _reflection.GeneratedProtocolMessageType('PartitionInfo', (_message.Message,), { + 'DESCRIPTOR' : _PARTITIONINFO, + '__module__' : 'update_metadata_pb2' # @@protoc_insertion_point(class_scope:chromeos_update_engine.PartitionInfo) - )) + }) _sym_db.RegisterMessage(PartitionInfo) -ImageInfo = _reflection.GeneratedProtocolMessageType('ImageInfo', (_message.Message,), dict( - DESCRIPTOR = _IMAGEINFO, - __module__ = 'update_metadata_pb2' +ImageInfo = _reflection.GeneratedProtocolMessageType('ImageInfo', (_message.Message,), { + 'DESCRIPTOR' : _IMAGEINFO, + '__module__' : 'update_metadata_pb2' # @@protoc_insertion_point(class_scope:chromeos_update_engine.ImageInfo) - )) + }) _sym_db.RegisterMessage(ImageInfo) -InstallOperation = _reflection.GeneratedProtocolMessageType('InstallOperation', (_message.Message,), dict( - DESCRIPTOR = _INSTALLOPERATION, - __module__ = 'update_metadata_pb2' +InstallOperation = _reflection.GeneratedProtocolMessageType('InstallOperation', (_message.Message,), { + 'DESCRIPTOR' : _INSTALLOPERATION, + '__module__' : 'update_metadata_pb2' # @@protoc_insertion_point(class_scope:chromeos_update_engine.InstallOperation) - )) + }) _sym_db.RegisterMessage(InstallOperation) -PartitionUpdate = _reflection.GeneratedProtocolMessageType('PartitionUpdate', (_message.Message,), dict( - DESCRIPTOR = _PARTITIONUPDATE, - __module__ = 'update_metadata_pb2' +PartitionUpdate = _reflection.GeneratedProtocolMessageType('PartitionUpdate', (_message.Message,), { + 'DESCRIPTOR' : _PARTITIONUPDATE, + '__module__' : 'update_metadata_pb2' # @@protoc_insertion_point(class_scope:chromeos_update_engine.PartitionUpdate) - )) + }) _sym_db.RegisterMessage(PartitionUpdate) -DynamicPartitionGroup = _reflection.GeneratedProtocolMessageType('DynamicPartitionGroup', (_message.Message,), dict( - DESCRIPTOR = _DYNAMICPARTITIONGROUP, - __module__ = 'update_metadata_pb2' +DynamicPartitionGroup = _reflection.GeneratedProtocolMessageType('DynamicPartitionGroup', (_message.Message,), { + 'DESCRIPTOR' : _DYNAMICPARTITIONGROUP, + '__module__' : 'update_metadata_pb2' # @@protoc_insertion_point(class_scope:chromeos_update_engine.DynamicPartitionGroup) - )) + }) _sym_db.RegisterMessage(DynamicPartitionGroup) -DynamicPartitionMetadata = _reflection.GeneratedProtocolMessageType('DynamicPartitionMetadata', (_message.Message,), dict( - DESCRIPTOR = _DYNAMICPARTITIONMETADATA, - __module__ = 'update_metadata_pb2' +DynamicPartitionMetadata = _reflection.GeneratedProtocolMessageType('DynamicPartitionMetadata', (_message.Message,), { + 'DESCRIPTOR' : _DYNAMICPARTITIONMETADATA, + '__module__' : 'update_metadata_pb2' # @@protoc_insertion_point(class_scope:chromeos_update_engine.DynamicPartitionMetadata) - )) + }) _sym_db.RegisterMessage(DynamicPartitionMetadata) -DeltaArchiveManifest = _reflection.GeneratedProtocolMessageType('DeltaArchiveManifest', (_message.Message,), dict( - DESCRIPTOR = _DELTAARCHIVEMANIFEST, - __module__ = 'update_metadata_pb2' +DeltaArchiveManifest = _reflection.GeneratedProtocolMessageType('DeltaArchiveManifest', (_message.Message,), { + 'DESCRIPTOR' : _DELTAARCHIVEMANIFEST, + '__module__' : 'update_metadata_pb2' # @@protoc_insertion_point(class_scope:chromeos_update_engine.DeltaArchiveManifest) - )) + }) _sym_db.RegisterMessage(DeltaArchiveManifest) DESCRIPTOR._options = None +_SIGNATURES_SIGNATURE.fields_by_name['version']._options = None _INSTALLOPERATION_TYPE.values_by_name["MOVE"]._options = None _INSTALLOPERATION_TYPE.values_by_name["BSDIFF"]._options = None _DELTAARCHIVEMANIFEST.fields_by_name['install_operations']._options = None diff --git a/service_delegate_android_interface.h b/service_delegate_android_interface.h index 5267bb06..34a97123 100644 --- a/service_delegate_android_interface.h +++ b/service_delegate_android_interface.h @@ -19,6 +19,7 @@ #include <inttypes.h> +#include <memory> #include <string> #include <vector> @@ -26,6 +27,18 @@ namespace chromeos_update_engine { +// See ServiceDelegateAndroidInterface.CleanupSuccessfulUpdate +// Wraps a IUpdateEngineCallback binder object used specifically for +// CleanupSuccessfulUpdate. +class CleanupSuccessfulUpdateCallbackInterface { + public: + virtual ~CleanupSuccessfulUpdateCallbackInterface() {} + virtual void OnCleanupProgressUpdate(double progress) = 0; + virtual void OnCleanupComplete(int32_t error_code) = 0; + // Call RegisterForDeathNotifications on the internal binder object. + virtual void RegisterForDeathNotifications(base::Closure unbind) = 0; +}; + // This class defines the interface exposed by the Android version of the // daemon service. This interface only includes the method calls that such // daemon exposes. For asynchronous events initiated by a class implementing @@ -47,6 +60,13 @@ class ServiceDelegateAndroidInterface { const std::vector<std::string>& key_value_pair_headers, brillo::ErrorPtr* error) = 0; + virtual bool ApplyPayload( + int fd, + int64_t payload_offset, + int64_t payload_size, + const std::vector<std::string>& key_value_pair_headers, + brillo::ErrorPtr* error) = 0; + // Suspend an ongoing update. Returns true if there was an update ongoing and // it was suspended. In case of failure, it returns false and sets |error| // accordingly. @@ -76,6 +96,28 @@ class ServiceDelegateAndroidInterface { virtual bool VerifyPayloadApplicable(const std::string& metadata_filename, brillo::ErrorPtr* error) = 0; + // Allocates space for a payload. + // Returns 0 if space is successfully preallocated. + // Return non-zero if not enough space is not available; returned value is + // the total space required (in bytes) to be free on the device for this + // update to be applied, and |error| is unset. + // In case of error, returns 0, and sets |error| accordingly. + // + // This function may block for several minutes in the worst case. + virtual uint64_t AllocateSpaceForPayload( + const std::string& metadata_filename, + const std::vector<std::string>& key_value_pair_headers, + brillo::ErrorPtr* error) = 0; + + // Wait for merge to complete, then clean up merge after an update has been + // successful. + // + // This function returns immediately. Progress updates are provided in + // |callback|. + virtual void CleanupSuccessfulUpdate( + std::unique_ptr<CleanupSuccessfulUpdateCallbackInterface> callback, + brillo::ErrorPtr* error) = 0; + protected: ServiceDelegateAndroidInterface() = default; }; diff --git a/sideload_main.cc b/sideload_main.cc index 818fa5c9..27967cda 100644 --- a/sideload_main.cc +++ b/sideload_main.cc @@ -20,7 +20,6 @@ #include <vector> #include <base/command_line.h> -#include <base/logging.h> #include <base/strings/string_split.h> #include <base/strings/stringprintf.h> #include <brillo/asynchronous_signal_handler.h> @@ -36,6 +35,7 @@ #include "update_engine/common/subprocess.h" #include "update_engine/common/terminator.h" #include "update_engine/common/utils.h" +#include "update_engine/logging.h" #include "update_engine/update_attempter_android.h" using std::string; @@ -46,17 +46,6 @@ using update_engine::UpdateStatus; namespace chromeos_update_engine { namespace { -void SetupLogging() { - string log_file; - logging::LoggingSettings log_settings; - log_settings.lock_log = logging::DONT_LOCK_LOG_FILE; - log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE; - log_settings.log_file = nullptr; - log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; - - logging::InitLogging(log_settings); -} - class SideloadDaemonState : public DaemonStateInterface, public ServiceObserverInterface { public: @@ -195,7 +184,7 @@ int main(int argc, char** argv) { DEFINE_int64(status_fd, -1, "A file descriptor to notify the update status."); chromeos_update_engine::Terminator::Init(); - chromeos_update_engine::SetupLogging(); + chromeos_update_engine::SetupLogging(true /* stderr */, false /* file */); brillo::FlagHelper::Init(argc, argv, "Update Engine Sideload"); LOG(INFO) << "Update Engine Sideloading starting"; diff --git a/stable/Android.bp b/stable/Android.bp new file mode 100644 index 00000000..337ae96e --- /dev/null +++ b/stable/Android.bp @@ -0,0 +1,65 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Stable AIDL interface between update_engine and other APEXes +// ======================================================== +aidl_interface { + name: "libupdate_engine_stable", + srcs: [ + "android/os/IUpdateEngineStable.aidl", + "android/os/IUpdateEngineStableCallback.aidl", + ], + backend: { + cpp: { + enabled: true, + }, + java: { + enabled: false, + }, + ndk: { + enabled: true, + apex_available: [ + "com.android.gki.*", + ], + }, + }, +} + +// update_engine_stable_client (type: executable) +// ======================================================== +// update_engine console client installed to APEXes +cc_binary { + name: "update_engine_stable_client", + + header_libs: [ + "libupdate_engine_headers", + ], + shared_libs: [ + "libbinder_ndk", + "libbase", + "liblog", + ], + static_libs: [ + "libgflags", + "libupdate_engine_stable-ndk_platform", + ], + srcs: [ + "update_engine_stable_client.cc", + ], + apex_available: [ + "com.android.gki.*", + ], +} diff --git a/stable/aidl_api/libupdate_engine_stable/current/android/os/IUpdateEngineStable.aidl b/stable/aidl_api/libupdate_engine_stable/current/android/os/IUpdateEngineStable.aidl new file mode 100644 index 00000000..82c3ca5f --- /dev/null +++ b/stable/aidl_api/libupdate_engine_stable/current/android/os/IUpdateEngineStable.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.os; +interface IUpdateEngineStable { + void applyPayloadFd(in ParcelFileDescriptor pfd, in long payload_offset, in long payload_size, in String[] headerKeyValuePairs); + boolean bind(android.os.IUpdateEngineStableCallback callback); + boolean unbind(android.os.IUpdateEngineStableCallback callback); +} diff --git a/stable/aidl_api/libupdate_engine_stable/current/android/os/IUpdateEngineStableCallback.aidl b/stable/aidl_api/libupdate_engine_stable/current/android/os/IUpdateEngineStableCallback.aidl new file mode 100644 index 00000000..4c72b495 --- /dev/null +++ b/stable/aidl_api/libupdate_engine_stable/current/android/os/IUpdateEngineStableCallback.aidl @@ -0,0 +1,22 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.os; +interface IUpdateEngineStableCallback { + oneway void onStatusUpdate(int status_code, float percentage); + oneway void onPayloadApplicationComplete(int error_code); +} diff --git a/stable/android/os/IUpdateEngineStable.aidl b/stable/android/os/IUpdateEngineStable.aidl new file mode 100644 index 00000000..b3b6674a --- /dev/null +++ b/stable/android/os/IUpdateEngineStable.aidl @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.os.IUpdateEngineStableCallback; +import android.os.ParcelFileDescriptor; + +/** + * The stable interface exposed by the update engine daemon. + */ +interface IUpdateEngineStable { + /** + * Apply the given payload as provided in the given file descriptor. + * + * See {@link #bind(IUpdateEngineCallback)} for status updates. + * + * @param pfd The file descriptor opened at the payload file. Note that the daemon must have + * enough permission to operate on the file descriptor. + * @param payload_offset offset into pfd where the payload binary starts. + * @param payload_size length after payload_offset to read from pfd. If 0, it will be auto + * detected. + * @param headerKeyValuePairs additional header key value pairs, in the format of "key=value". + * @see android.os.UpdateEngine#applyPayload(android.content.res.AssetFileDescriptor, String[]) + */ + void applyPayloadFd(in ParcelFileDescriptor pfd, + in long payload_offset, + in long payload_size, + in String[] headerKeyValuePairs); + + /** + * Bind a callback for status updates on payload application. + * + * At any given time, only one callback can be bound. If a callback is already bound, + * subsequent binding will fail and return false until the bound callback is unbound. That is, + * binding is first-come, first-serve. + * + * A bound callback may be unbound explicitly by calling + * {@link #unbind(IUpdateEngineStableCallback)}, or + * implicitly when the process implementing the callback dies. + * + * @param callback See {@link IUpdateEngineStableCallback} + * @return true if binding is successful, false otherwise. + * @see android.os.UpdateEngine#bind(android.os.UpdateEngineCallback) + */ + boolean bind(IUpdateEngineStableCallback callback); + + /** + * Unbind a possibly bound callback. + * + * If the provided callback does not match the previously bound callback, unbinding fails. + * + * Note that a callback may also be unbound when the process implementing the callback dies. + * Hence, a client usually does not need to explicitly unbind a callback unless it wants to change + * the bound callback. + * + * @param callback The callback to be unbound. See {@link IUpdateEngineStableCallback}. + * @return true if unbinding is successful, false otherwise. + * @see android.os.UpdateEngine#unbind(android.os.UpdateEngineCallback) + */ + boolean unbind(IUpdateEngineStableCallback callback); +} diff --git a/stable/android/os/IUpdateEngineStableCallback.aidl b/stable/android/os/IUpdateEngineStableCallback.aidl new file mode 100644 index 00000000..d8fc3335 --- /dev/null +++ b/stable/android/os/IUpdateEngineStableCallback.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** + * The stable Callback interface for IUpdateEngineStable. + */ +oneway interface IUpdateEngineStableCallback { + /** + * Invoked when a payload is being applied and there is a status update. + * + * @param status_code see {@link android.os.UpdateEngine.UpdateStatusConstants}. + * @param percentage percentage of progress of the current stage. + * @see android.os.UpdateEngineCallback#onStatusUpdate(int, float) + */ + void onStatusUpdate(int status_code, float percentage); + + /** + * Invoked when a payload has finished being applied. + * + * @param error_code see {@link android.os.UpdateEngine.ErrorCodeConstants} + * @see android.os.UpdateEngineCallback#onPayloadApplicationComplete(int) + */ + void onPayloadApplicationComplete(int error_code); +} diff --git a/stable/update_engine_stable_client.cc b/stable/update_engine_stable_client.cc new file mode 100644 index 00000000..da203c4c --- /dev/null +++ b/stable/update_engine_stable_client.cc @@ -0,0 +1,188 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// update_engine console client installed to APEXes for scripts to invoke +// directly. Uses the stable API. + +#include <fcntl.h> +#include <sysexits.h> +#include <unistd.h> + +#include <vector> + +#include <aidl/android/os/BnUpdateEngineStableCallback.h> +#include <aidl/android/os/IUpdateEngineStable.h> +#include <android-base/logging.h> +#include <android-base/strings.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> +#include <android/binder_ibinder.h> +#include <common/error_code.h> +#include <gflags/gflags.h> +#include <utils/StrongPointer.h> + +namespace chromeos_update_engine::internal { + +DEFINE_string(payload, + "file:///path/to/payload.bin", + "The file URI to the update payload to use, or path to the file"); +DEFINE_int64(offset, + 0, + "The offset in the payload where the CrAU update starts."); +DEFINE_int64(size, + 0, + "The size of the CrAU part of the payload. If 0 is passed, it " + "will be autodetected."); +DEFINE_string(headers, + "", + "A list of key-value pairs, one element of the list per line."); + +[[noreturn]] int Exit(int return_code) { + LOG(INFO) << "Exit: " << return_code; + exit(return_code); +} +// Called whenever the UpdateEngine daemon dies. +void UpdateEngineServiceDied(void*) { + LOG(ERROR) << "UpdateEngineService died."; + Exit(EX_SOFTWARE); +} + +class UpdateEngineClientAndroid { + public: + UpdateEngineClientAndroid() = default; + int Run(); + + private: + class UECallback : public aidl::android::os::BnUpdateEngineStableCallback { + public: + UECallback() = default; + + // android::os::BnUpdateEngineStableCallback overrides. + ndk::ScopedAStatus onStatusUpdate(int status_code, float progress) override; + ndk::ScopedAStatus onPayloadApplicationComplete(int error_code) override; + }; + + static std::vector<std::string> ParseHeaders(const std::string& arg); + + const ndk::ScopedAIBinder_DeathRecipient death_recipient_{ + AIBinder_DeathRecipient_new(&UpdateEngineServiceDied)}; + std::shared_ptr<aidl::android::os::IUpdateEngineStable> service_; + std::shared_ptr<aidl::android::os::BnUpdateEngineStableCallback> callback_; +}; + +ndk::ScopedAStatus UpdateEngineClientAndroid::UECallback::onStatusUpdate( + int status_code, float progress) { + LOG(INFO) << "onStatusUpdate(" << status_code << ", " << progress << ")"; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus +UpdateEngineClientAndroid::UECallback::onPayloadApplicationComplete( + int error_code) { + LOG(INFO) << "onPayloadApplicationComplete(" << error_code << ")"; + auto code = static_cast<ErrorCode>(error_code); + Exit((code == ErrorCode::kSuccess || code == ErrorCode::kUpdatedButNotActive) + ? EX_OK + : EX_SOFTWARE); +} + +int UpdateEngineClientAndroid::Run() { + service_ = aidl::android::os::IUpdateEngineStable::fromBinder(ndk::SpAIBinder( + AServiceManager_getService("android.os.UpdateEngineStableService"))); + if (service_ == nullptr) { + LOG(ERROR) + << "Failed to get IUpdateEngineStable binder from service manager."; + return EX_SOFTWARE; + } + + // Register a callback object with the service. + callback_ = ndk::SharedRefBase::make<UECallback>(); + bool bound; + if (!service_->bind(callback_, &bound).isOk() || !bound) { + LOG(ERROR) << "Failed to bind() the UpdateEngine daemon."; + return EX_SOFTWARE; + } + + auto headers = ParseHeaders(FLAGS_headers); + ndk::ScopedAStatus status; + const char* payload_path; + std::string file_prefix = "file://"; + if (android::base::StartsWith(FLAGS_payload, file_prefix)) { + payload_path = FLAGS_payload.data() + file_prefix.length(); + } else { + payload_path = FLAGS_payload.data(); + } + ndk::ScopedFileDescriptor ufd( + TEMP_FAILURE_RETRY(open(payload_path, O_RDONLY))); + if (ufd.get() < 0) { + PLOG(ERROR) << "Can't open " << payload_path; + return EX_SOFTWARE; + } + status = service_->applyPayloadFd(ufd, FLAGS_offset, FLAGS_size, headers); + if (!status.isOk()) { + LOG(ERROR) << "Cannot apply payload: " << status.getDescription(); + return EX_SOFTWARE; + } + + // When following updates status changes, exit if the update_engine daemon + // dies. + if (AIBinder_linkToDeath(service_->asBinder().get(), + death_recipient_.get(), + nullptr) != STATUS_OK) { + return EX_SOFTWARE; + } + + return EX_OK; +} + +std::vector<std::string> UpdateEngineClientAndroid::ParseHeaders( + const std::string& arg) { + std::vector<std::string> lines = android::base::Split(arg, "\n"); + std::vector<std::string> headers; + for (const auto& line : lines) { + auto header = android::base::Trim(line); + if (!header.empty()) { + headers.push_back(header); + } + } + return headers; +} + +} // namespace chromeos_update_engine::internal + +int main(int argc, char** argv) { + android::base::InitLogging(argv); + gflags::ParseCommandLineFlags(&argc, &argv, true); + + // Unlike other update_engine* processes that uses message loops, + // update_engine_stable_client uses a thread pool model. However, number of + // threads is limited to 1; that is, 0 additional threads should be spawned. + // This avoids some race conditions. + if (!ABinderProcess_setThreadPoolMaxThreadCount(0)) { + LOG(ERROR) << "Cannot set thread pool max thread count"; + return EX_SOFTWARE; + } + ABinderProcess_startThreadPool(); + + chromeos_update_engine::internal::UpdateEngineClientAndroid client{}; + int code = client.Run(); + if (code != EX_OK) + return code; + + ABinderProcess_joinThreadPool(); + LOG(ERROR) << "Exited from joinThreadPool."; + return EX_SOFTWARE; +} diff --git a/test_config.xml b/test_config.xml new file mode 100644 index 00000000..fe3cbfda --- /dev/null +++ b/test_config.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Config to run update_engine_unittests on device"> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="update_engine_unittests->/data/nativetest/update_engine_unittests" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/nativetest" /> + <!-- The following rules avoid test runner from calling the following helper executables + directly as gtests. --> + <option name="file-exclusion-filter-regex" value=".*/delta_generator$" /> + <option name="file-exclusion-filter-regex" value=".*/test_http_server$" /> + <option name="file-exclusion-filter-regex" value=".*/test_subprocess$" /> + <option name="module-name" value="update_engine_unittests" /> + </test> +</configuration> diff --git a/test_http_server.cc b/test_http_server.cc index 4fc89e53..cf6f10db 100644 --- a/test_http_server.cc +++ b/test_http_server.cc @@ -189,7 +189,8 @@ ssize_t WriteHeaders(int fd, ret = WriteString(fd, string("HTTP/1.1 ") + Itoa(return_code) + " " + GetHttpResponseDescription(return_code) + - EOL "Content-Type: application/octet-stream" EOL); + EOL "Content-Type: application/octet-stream" EOL + "Connection: close" EOL); if (ret < 0) return -1; written += ret; @@ -406,6 +407,7 @@ void HandleRedirect(int fd, const HttpRequest& request) { if ((ret = WriteString(fd, "HTTP/1.1 " + Itoa(code) + " " + status + EOL)) < 0) return; + WriteString(fd, "Connection: close" EOL); WriteString(fd, "Location: " + url + EOL); } diff --git a/unittest_key_EC.pem b/unittest_key_EC.pem new file mode 100644 index 00000000..9e65a68a --- /dev/null +++ b/unittest_key_EC.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgGaguGj8Yb1KkqKHd +ISblUsjtOCbzAuVpX81i02sm8FWhRANCAARBnuotwKOsuvjH6iwTDhOAi7Q5pLWz +xDkZjg2pcfbfi9FFTvLYETas7B2W6fx9PUezUmHTFTDV2JZuMYYFdZOw +-----END PRIVATE KEY----- diff --git a/unittest_key_RSA4096.pem b/unittest_key_RSA4096.pem new file mode 100644 index 00000000..56139105 --- /dev/null +++ b/unittest_key_RSA4096.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKgIBAAKCAgEAu8vBB+DvDPWgO1IWli5VuXHkMWYtD+wzToKIP/NkiGYdf1b9 +EvCgrvS3J4CutKCHoz1N5Pc29DKZduDhLbqWzqvUldJ8kSKd967PRCdM5vJHmdox +H8E7m3YROBrppcJG32B5TD5qbn6jqrsSnwjlGZN2RAuURQwalSxq3/ZttiEzEMDR +o/7v5rQINlF0Rsud+HlNXrxzhR/LQyMV3d+/JvoEWPmz4unzpKyYAMOjdNTcMLB0 +ccULFYKfJtRCqTyfgEUbT+mGLTbDQSkzl6mcNfDg+3hu8lTjQH+6EjjDUSu6oOmV +OmDf8tZPmGRdw/9R1PUx6A1yKQR6FkdkqRDOt1GvIpUpsfT36Gg59u1a6YyHKudB +6igFzr3JcTgslQ9KUZK68j6Xr2AlbsffyJ5ltyuHT4gMtCppkuGUi1CZ6+i55LfA +t1cVx6meE+zJYK46zu9GKgsXg72BNCi/3v1HievZwDcm5A04YQZMZDgtb74pN1Vz +uCEzxCgpyx3b3Y/K8JI6n9xMeQNIPF2h9JeiO9qasV4vJ51PbnS61BHR1E9Sh845 +2QVT+mqJnA8uRp22+sF0wU5Ae33UAZxnKk9Ax+uz6iRP6wlILlUpl+Chsjla6+Ut +RkE4NRJh2xdin+j9GCaXFVOzx0RPLAYrD9/cM6BOV7CKZ/2iqVnaXH6sY00CAwEA +AQKCAgEAgf77ci68i6YD8sxSIkeUw7gZFDD8hIcmxPQKyOn874ZwaNOYf+Hd+P4h +QtELHrH48oDfSN3wRn44SVhFx9ffyNSdZdC8teLWort5cl4aF8wi0Fd/pdGnJ2hF +ZycKEdo4ISyxCpwyJKa5ONgifcA0hs3TEitJybolNJn4qWv2ahr1NGWlOPairnp1 +LNSZvt/4TCX77tZYyRBHLgQ9gMb/lUWAeO7xHOSB6b4nwm+q2Jb4jSO4l4CkuZEg +BkrskiYK60nrLBgk72t3IcYZlqSsI5LIyoqFkm48mUtRTIfKfIfeusC2siCZJYpA +subXGXPF+7p3f4C/Q7F7qaxl+7pMvN2UtnCY4lYppMKETquVT2vtEgZjSVkQTb0X +wEYmipMsonVcSLL4xTQxT0KdY2JczH7xicIJwolED+eob64t4q64ljfC3tlPJakE +O2GgyynLH+oojBsH8XCvuhODFL+/e1eQmV0gu0az3fcHY+lCKrYEhlVB9wVe5Afn +2GH71deZKY+iF2E8RJXwGdmpN4PbLyWepqCm0TdMUrn5A37TBr++3B9d7/eyMt1o +afMxDzAZ27HaT6eg6SB/LJdkezvs67jLcur18NFMevSxKc+G+B8g+7euc00sKaLu +WIX2bf8kc6L2sLECERpXOBNRSQxY33vS72szF6P7rN0+Szb+aQECggEBAOSqtUSY +DJbyjnxmhGodcbuubNyNZRrs/vXNcYHmBZbWvR9rxtWgA1LYJiAY4wvtc+Ciw/Y6 +468MG6ip4rLu/6lDkekH/ARxW6zErCfCDDg91cP1FXP/Cwo6COJ9V2imCbTsiMSi +bVigG4Lb4FTgBkreOyxDjnIVzZwCrwtxZWpNwA2GlEBS6IsJqelnUi/roi4rLpCj +Y5mLvL8YYPduead9TwesYsXdK2qBf6A034GNXxvzhV70HfhnI60ydi6pNRrWamru +TBJEuY7CipzyevqM3drfkFZDKyEBEVnk7We4IpiaOkBfLsshvFqk9asWzts1eDa8 +GpOqM0RYRCXZya0CggEBANI+YWOrK2qSLxFBbCupNaxG++/tafOXsgjkmGaRxCHt +IcvPTIzFBQOSCobF51KoZIriVEVKw1bXHFlibeQ1IEuh8yVBo3l6NO8JKlc/tdJf +pfkUh5XjQWJW8UqWULb5CkJCEheenG0oy8zhjOERPDcQXRYOhIo8JSpWfJFtWSWk +L/X7kfkEvQxV0omFCUg4sCxdBeqIEItYd0Td0SCmHPZIs2KgSmpLIPBH0BMibNkY +ZeSaz5nWbw06Unhkas+ulm3S+IEjb7neuAWGPlIXnPch9hw2pdZf49XRW4fjc7Nr ++G+U2Jgjv81+Rn7nFK2Whh22XKL5aP2myoVESlvzdCECggEBAIc9DwgKhSehPPQG +DbpUv7coaennFizejcwCPWd+C0AysJesvmQJxu1wONwy29VqEmaA3TT7jz0wBAu0 +rgb1ou8Qr3MK7doS0Q1VJBw/f8qjh5HlmVKJPJZHzIlnaBLUYFlIq3rgNZt81ciH +Eh4ggJg25vg+3DhM/NWQIMa7wick5LkbJwMEBdR1WrBYExuUWM7FazzP5VAifPbo +DDFKfVi5m8wGAETVkZ/kBv9RRf7xBZcaZ37JEhCfr1H3zj26hVXiCf5EAWmsi7IL +DL/WCTW1qmCQaGUcRJ24a/KmmmIFXTCzxk/b+2jYAvX5KfKOArlS3k5A4dcDil6Z +dXSNYeECggEBAIHzRMcSOde5W5ZS1cV25VIC3h5CpMaH8OdGRFzBpHVD2SvcifhI +nvzB+/epw3130A14L5ZUy8CVXVRyXnI71CZrh5pzo9OmEaneBGnBW2UY8cGvSs7+ +lJ9wFdyAZIt0Cz9BD2XCB/YAzVdp4mYK/Skb2C2V855t5prwsjZBXGTDw1FLmcJN +h3xkX6nYrRAS2fHR3aJFT9SRbccHRAfmJOilrxs68EQbA9UAzj/Fe3oEdpaCiecQ +f7uxXOBFUS/lPd3MFQXdHWXJn/zqKQMczUyDlVeC/6YtxumWafjoQc+Y4Qo2+lmv +XxJpBrHRqxpQe71JxqCFgLunqG4O89c594ECggEAcMlYhrO2+R7mEPFDZLwSvTMV +BOof6hxLIW8PkLQ/8HHTTacC02sKFiR921sw9NadQ7G0cf23Fecmolg4brJUh9sQ +evjdYMdqIYPJT5hYSkIkdTk0Ny+tN2Pt4fBTTv3N2D3Da/5ODfrVSj0ib89DXG5D +bPahlFLIhKaVbXNe1RQL/8j4nFf8D9LwuEMOMYrUpSMw9ULT5dB34QN2TOnwW9JW +Md7aSY5pK1j1Y8FoWCAFSw+o+yWq5DbTFvcEhttWrUoFl9YxTolbLt6sw6TLy12x +9haQDvbfvRkg3Es31DEC8plsltfg5S9KwRqCchKKUm7cnAJFhB2/2C6JX2k0XQ== +-----END RSA PRIVATE KEY----- diff --git a/update_attempter.cc b/update_attempter.cc index 0f0605d5..24562e2a 100644 --- a/update_attempter.cc +++ b/update_attempter.cc @@ -824,8 +824,8 @@ void UpdateAttempter::BuildUpdateActions(bool interactive) { system_state_->hardware()), false, session_id_); - auto filesystem_verifier_action = - std::make_unique<FilesystemVerifierAction>(); + auto filesystem_verifier_action = std::make_unique<FilesystemVerifierAction>( + system_state_->boot_control()->GetDynamicPartitionControl()); auto update_complete_action = std::make_unique<OmahaRequestAction>( system_state_, new OmahaEvent(OmahaEvent::kTypeUpdateComplete), @@ -1383,6 +1383,7 @@ void UpdateAttempter::ActionCompleted(ActionProcessor* processor, case UpdateStatus::REPORTING_ERROR_EVENT: case UpdateStatus::ATTEMPTING_ROLLBACK: case UpdateStatus::DISABLED: + case UpdateStatus::CLEANUP_PREVIOUS_UPDATE: MarkDeltaUpdateFailure(); break; } diff --git a/update_attempter_android.cc b/update_attempter_android.cc index c738e4ef..7fc13e11 100644 --- a/update_attempter_android.cc +++ b/update_attempter_android.cc @@ -22,6 +22,7 @@ #include <utility> #include <android-base/properties.h> +#include <android-base/unique_fd.h> #include <base/bind.h> #include <base/logging.h> #include <base/strings/string_number_conversions.h> @@ -30,6 +31,7 @@ #include <brillo/strings/string_utils.h> #include <log/log_safetynet.h> +#include "update_engine/cleanup_previous_update_action.h" #include "update_engine/common/constants.h" #include "update_engine/common/error_code_utils.h" #include "update_engine/common/file_fetcher.h" @@ -38,6 +40,7 @@ #include "update_engine/metrics_reporter_interface.h" #include "update_engine/metrics_utils.h" #include "update_engine/network_selector.h" +#include "update_engine/payload_consumer/certificate_parser_interface.h" #include "update_engine/payload_consumer/delta_performer.h" #include "update_engine/payload_consumer/download_action.h" #include "update_engine/payload_consumer/file_descriptor.h" @@ -45,6 +48,7 @@ #include "update_engine/payload_consumer/filesystem_verifier_action.h" #include "update_engine/payload_consumer/payload_constants.h" #include "update_engine/payload_consumer/payload_metadata.h" +#include "update_engine/payload_consumer/payload_verifier.h" #include "update_engine/payload_consumer/postinstall_runner_action.h" #include "update_engine/update_boot_flags_action.h" #include "update_engine/update_status_utils.h" @@ -55,6 +59,7 @@ #include "update_engine/libcurl_http_fetcher.h" #endif +using android::base::unique_fd; using base::Bind; using base::Time; using base::TimeDelta; @@ -94,6 +99,34 @@ bool GetHeaderAsBool(const string& header, bool default_value) { return default_value; } +bool ParseKeyValuePairHeaders(const vector<string>& key_value_pair_headers, + std::map<string, string>* headers, + brillo::ErrorPtr* error) { + for (const string& key_value_pair : key_value_pair_headers) { + string key; + string value; + if (!brillo::string_utils::SplitAtFirst( + key_value_pair, "=", &key, &value, false)) { + return LogAndSetError( + error, FROM_HERE, "Passed invalid header: " + key_value_pair); + } + if (!headers->emplace(key, value).second) + return LogAndSetError(error, FROM_HERE, "Passed repeated key: " + key); + } + return true; +} + +// Unique identifier for the payload. An empty string means that the payload +// can't be resumed. +string GetPayloadId(const std::map<string, string>& headers) { + return (headers.count(kPayloadPropertyFileHash) + ? headers.at(kPayloadPropertyFileHash) + : "") + + (headers.count(kPayloadPropertyMetadataHash) + ? headers.at(kPayloadPropertyMetadataHash) + : ""); +} + } // namespace UpdateAttempterAndroid::UpdateAttempterAndroid( @@ -125,6 +158,12 @@ void UpdateAttempterAndroid::Init() { } else { SetStatusAndNotify(UpdateStatus::IDLE); UpdatePrefsAndReportUpdateMetricsOnReboot(); +#ifdef _UE_SIDELOAD + LOG(INFO) << "Skip ScheduleCleanupPreviousUpdate in sideload because " + << "ApplyPayload will call it later."; +#else + ScheduleCleanupPreviousUpdate(); +#endif } } @@ -145,22 +184,11 @@ bool UpdateAttempterAndroid::ApplyPayload( DCHECK(status_ == UpdateStatus::IDLE); std::map<string, string> headers; - for (const string& key_value_pair : key_value_pair_headers) { - string key; - string value; - if (!brillo::string_utils::SplitAtFirst( - key_value_pair, "=", &key, &value, false)) { - return LogAndSetError( - error, FROM_HERE, "Passed invalid header: " + key_value_pair); - } - if (!headers.emplace(key, value).second) - return LogAndSetError(error, FROM_HERE, "Passed repeated key: " + key); + if (!ParseKeyValuePairHeaders(key_value_pair_headers, &headers, error)) { + return false; } - // Unique identifier for the payload. An empty string means that the payload - // can't be resumed. - string payload_id = (headers[kPayloadPropertyFileHash] + - headers[kPayloadPropertyMetadataHash]); + string payload_id = GetPayloadId(headers); // Setup the InstallPlan based on the request. install_plan_ = InstallPlan(); @@ -196,15 +224,22 @@ bool UpdateAttempterAndroid::ApplyPayload( install_plan_.is_resume = !payload_id.empty() && DeltaPerformer::CanResumeUpdate(prefs_, payload_id); if (!install_plan_.is_resume) { - if (!DeltaPerformer::ResetUpdateProgress(prefs_, false)) { + // No need to reset dynamic_partititon_metadata_updated. If previous calls + // to AllocateSpaceForPayload uses the same payload_id, reuse preallocated + // space. Otherwise, DeltaPerformer re-allocates space when the payload is + // applied. + if (!DeltaPerformer::ResetUpdateProgress( + prefs_, + false /* quick */, + true /* skip_dynamic_partititon_metadata_updated */)) { LOG(WARNING) << "Unable to reset the update progress."; } if (!prefs_->SetString(kPrefsUpdateCheckResponseHash, payload_id)) { LOG(WARNING) << "Unable to save the update check response hash."; } } - install_plan_.source_slot = boot_control_->GetCurrentSlot(); - install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0; + install_plan_.source_slot = GetCurrentSlot(); + install_plan_.target_slot = GetTargetSlot(); install_plan_.powerwash_required = GetHeaderAsBool(headers[kPayloadPropertyPowerwash], false); @@ -212,20 +247,8 @@ bool UpdateAttempterAndroid::ApplyPayload( install_plan_.switch_slot_on_reboot = GetHeaderAsBool(headers[kPayloadPropertySwitchSlotOnReboot], true); - install_plan_.run_post_install = true; - // Optionally skip post install if and only if: - // a) we're resuming - // b) post install has already succeeded before - // c) RUN_POST_INSTALL is set to 0. - if (install_plan_.is_resume && prefs_->Exists(kPrefsPostInstallSucceeded)) { - bool post_install_succeeded = false; - if (prefs_->GetBoolean(kPrefsPostInstallSucceeded, - &post_install_succeeded) && - post_install_succeeded) { - install_plan_.run_post_install = - GetHeaderAsBool(headers[kPayloadPropertyRunPostInstall], true); - } - } + install_plan_.run_post_install = + GetHeaderAsBool(headers[kPayloadPropertyRunPostInstall], true); // Skip writing verity if we're resuming and verity has already been written. install_plan_.write_verity = true; @@ -288,6 +311,19 @@ bool UpdateAttempterAndroid::ApplyPayload( return true; } +bool UpdateAttempterAndroid::ApplyPayload( + int fd, + int64_t payload_offset, + int64_t payload_size, + const vector<string>& key_value_pair_headers, + brillo::ErrorPtr* error) { + payload_fd_.reset(dup(fd)); + const string payload_url = "fd://" + std::to_string(payload_fd_.get()); + + return ApplyPayload( + payload_url, payload_offset, payload_size, key_value_pair_headers, error); +} + bool UpdateAttempterAndroid::SuspendUpdate(brillo::ErrorPtr* error) { if (!processor_->IsRunning()) return LogAndSetError(error, FROM_HERE, "No ongoing update to suspend."); @@ -314,18 +350,19 @@ bool UpdateAttempterAndroid::ResetStatus(brillo::ErrorPtr* error) { << UpdateStatusToString(status_) << " to UpdateStatus::IDLE"; switch (status_) { - case UpdateStatus::IDLE: + case UpdateStatus::IDLE: { + if (!boot_control_->GetDynamicPartitionControl()->ResetUpdate(prefs_)) { + LOG(WARNING) << "Failed to reset snapshots. UpdateStatus is IDLE but" + << "space might not be freed."; + } return true; + } case UpdateStatus::UPDATED_NEED_REBOOT: { - // Remove the reboot marker so that if the machine is rebooted - // after resetting to idle state, it doesn't go back to - // UpdateStatus::UPDATED_NEED_REBOOT state. - bool ret_value = prefs_->Delete(kPrefsUpdateCompletedOnBootId); - ClearMetricsPrefs(); + bool ret_value = true; // Update the boot flags so the current slot has higher priority. - if (!boot_control_->SetActiveBootSlot(boot_control_->GetCurrentSlot())) + if (!boot_control_->SetActiveBootSlot(GetCurrentSlot())) ret_value = false; // Mark the current slot as successful again, since marking it as active @@ -334,6 +371,20 @@ bool UpdateAttempterAndroid::ResetStatus(brillo::ErrorPtr* error) { if (!boot_control_->MarkBootSuccessfulAsync(Bind([](bool successful) {}))) ret_value = false; + // Resets the warm reset property since we won't switch the slot. + hardware_->SetWarmReset(false); + + // Remove update progress for DeltaPerformer and remove snapshots. + if (!boot_control_->GetDynamicPartitionControl()->ResetUpdate(prefs_)) + ret_value = false; + + // Remove the reboot marker so that if the machine is rebooted + // after resetting to idle state, it doesn't go back to + // UpdateStatus::UPDATED_NEED_REBOOT state. + if (!prefs_->Delete(kPrefsUpdateCompletedOnBootId)) + ret_value = false; + ClearMetricsPrefs(); + if (!ret_value) { return LogAndSetError( error, FROM_HERE, "Failed to reset the status to "); @@ -352,8 +403,10 @@ bool UpdateAttempterAndroid::ResetStatus(brillo::ErrorPtr* error) { } } -bool UpdateAttempterAndroid::VerifyPayloadApplicable( - const std::string& metadata_filename, brillo::ErrorPtr* error) { +bool UpdateAttempterAndroid::VerifyPayloadParseManifest( + const std::string& metadata_filename, + DeltaArchiveManifest* manifest, + brillo::ErrorPtr* error) { FileDescriptorPtr fd(new EintrSafeFileDescriptor); if (!fd->Open(metadata_filename.c_str(), O_RDONLY)) { return LogAndSetError( @@ -395,24 +448,39 @@ bool UpdateAttempterAndroid::VerifyPayloadApplicable( } fd->Close(); - string public_key; - if (!utils::ReadFile(constants::kUpdatePayloadPublicKeyPath, &public_key)) { - return LogAndSetError(error, FROM_HERE, "Failed to read public key."); + auto payload_verifier = PayloadVerifier::CreateInstanceFromZipPath( + constants::kUpdateCertificatesPath); + if (!payload_verifier) { + return LogAndSetError(error, + FROM_HERE, + "Failed to create the payload verifier from " + + std::string(constants::kUpdateCertificatesPath)); } - errorcode = - payload_metadata.ValidateMetadataSignature(metadata, "", public_key); + errorcode = payload_metadata.ValidateMetadataSignature( + metadata, "", *payload_verifier); if (errorcode != ErrorCode::kSuccess) { return LogAndSetError(error, FROM_HERE, "Failed to validate metadata signature: " + utils::ErrorCodeToString(errorcode)); } - DeltaArchiveManifest manifest; - if (!payload_metadata.GetManifest(metadata, &manifest)) { + if (!payload_metadata.GetManifest(metadata, manifest)) { return LogAndSetError(error, FROM_HERE, "Failed to parse manifest."); } - BootControlInterface::Slot current_slot = boot_control_->GetCurrentSlot(); + return true; +} + +bool UpdateAttempterAndroid::VerifyPayloadApplicable( + const std::string& metadata_filename, brillo::ErrorPtr* error) { + DeltaArchiveManifest manifest; + TEST_AND_RETURN_FALSE( + VerifyPayloadParseManifest(metadata_filename, &manifest, error)); + + FileDescriptorPtr fd(new EintrSafeFileDescriptor); + ErrorCode errorcode; + + BootControlInterface::Slot current_slot = GetCurrentSlot(); for (const PartitionUpdate& partition : manifest.partitions()) { if (!partition.has_old_partition_info()) continue; @@ -453,6 +521,11 @@ void UpdateAttempterAndroid::ProcessingDone(const ActionProcessor* processor, ErrorCode code) { LOG(INFO) << "Processing Done."; + if (status_ == UpdateStatus::CLEANUP_PREVIOUS_UPDATE) { + TerminateUpdateAndNotify(code); + return; + } + switch (code) { case ErrorCode::kSuccess: // Update succeeded. @@ -497,9 +570,15 @@ void UpdateAttempterAndroid::ActionCompleted(ActionProcessor* processor, // Reset download progress regardless of whether or not the download // action succeeded. const string type = action->Type(); - if (type == DownloadAction::StaticType()) { - download_progress_ = 0; + if (type == CleanupPreviousUpdateAction::StaticType() || + (type == NoOpAction::StaticType() && + status_ == UpdateStatus::CLEANUP_PREVIOUS_UPDATE)) { + cleanup_previous_update_code_ = code; + NotifyCleanupPreviousUpdateCallbacksAndClear(); } + // download_progress_ is actually used by other actions, such as + // filesystem_verify_action. Therefore we always clear it. + download_progress_ = 0; if (type == PostinstallRunnerAction::StaticType()) { bool succeeded = code == ErrorCode::kSuccess || code == ErrorCode::kUpdatedButNotActive; @@ -509,9 +588,15 @@ void UpdateAttempterAndroid::ActionCompleted(ActionProcessor* processor, // If an action failed, the ActionProcessor will cancel the whole thing. return; } + if (type == UpdateBootFlagsAction::StaticType()) { + SetStatusAndNotify(UpdateStatus::CLEANUP_PREVIOUS_UPDATE); + } if (type == DownloadAction::StaticType()) { - SetStatusAndNotify(UpdateStatus::FINALIZING); + auto download_action = static_cast<DownloadAction*>(action); + install_plan_ = *download_action->install_plan(); + SetStatusAndNotify(UpdateStatus::VERIFYING); } else if (type == FilesystemVerifierAction::StaticType()) { + SetStatusAndNotify(UpdateStatus::FINALIZING); prefs_->SetBoolean(kPrefsVerityWritten, true); } } @@ -562,6 +647,11 @@ void UpdateAttempterAndroid::ProgressUpdate(double progress) { } } +void UpdateAttempterAndroid::OnVerifyProgressUpdate(double progress) { + assert(status_ == UpdateStatus::VERIFYING); + ProgressUpdate(progress); +} + void UpdateAttempterAndroid::ScheduleProcessingStart() { LOG(INFO) << "Scheduling an action processor start."; brillo::MessageLoop::current()->PostTask( @@ -576,13 +666,22 @@ void UpdateAttempterAndroid::TerminateUpdateAndNotify(ErrorCode error_code) { return; } - boot_control_->Cleanup(); + if (status_ == UpdateStatus::CLEANUP_PREVIOUS_UPDATE) { + LOG(INFO) << "Terminating cleanup previous update."; + SetStatusAndNotify(UpdateStatus::IDLE); + for (auto observer : daemon_state_->service_observers()) + observer->SendPayloadApplicationComplete(error_code); + return; + } + + boot_control_->GetDynamicPartitionControl()->Cleanup(); download_progress_ = 0; UpdateStatus new_status = (error_code == ErrorCode::kSuccess ? UpdateStatus::UPDATED_NEED_REBOOT : UpdateStatus::IDLE); SetStatusAndNotify(new_status); + payload_fd_.reset(); // The network id is only applicable to one download attempt and once it's // done the network id should not be re-used anymore. @@ -596,6 +695,9 @@ void UpdateAttempterAndroid::TerminateUpdateAndNotify(ErrorCode error_code) { CollectAndReportUpdateMetricsOnUpdateFinished(error_code); ClearMetricsPrefs(); if (error_code == ErrorCode::kSuccess) { + // We should only reset the PayloadAttemptNumber if the update succeeds, or + // we switch to a different payload. + prefs_->Delete(kPrefsPayloadAttemptNumber); metrics_utils::SetSystemUpdatedMarker(clock_.get(), prefs_); // Clear the total bytes downloaded if and only if the update succeeds. prefs_->SetInt64(kPrefsTotalBytesDownloaded, 0); @@ -623,6 +725,9 @@ void UpdateAttempterAndroid::BuildUpdateActions(HttpFetcher* fetcher) { // Actions: auto update_boot_flags_action = std::make_unique<UpdateBootFlagsAction>(boot_control_); + auto cleanup_previous_update_action = + boot_control_->GetDynamicPartitionControl() + ->GetCleanupPreviousUpdateAction(boot_control_, prefs_, this); auto install_plan_action = std::make_unique<InstallPlanAction>(install_plan_); auto download_action = std::make_unique<DownloadAction>(prefs_, @@ -633,10 +738,11 @@ void UpdateAttempterAndroid::BuildUpdateActions(HttpFetcher* fetcher) { true /* interactive */); download_action->set_delegate(this); download_action->set_base_offset(base_offset_); - auto filesystem_verifier_action = - std::make_unique<FilesystemVerifierAction>(); + auto filesystem_verifier_action = std::make_unique<FilesystemVerifierAction>( + boot_control_->GetDynamicPartitionControl()); auto postinstall_runner_action = std::make_unique<PostinstallRunnerAction>(boot_control_, hardware_); + filesystem_verifier_action->set_delegate(this); postinstall_runner_action->set_delegate(this); // Bond them together. We have to use the leaf-types when calling @@ -647,6 +753,7 @@ void UpdateAttempterAndroid::BuildUpdateActions(HttpFetcher* fetcher) { postinstall_runner_action.get()); processor_->EnqueueAction(std::move(update_boot_flags_action)); + processor_->EnqueueAction(std::move(cleanup_previous_update_action)); processor_->EnqueueAction(std::move(install_plan_action)); processor_->EnqueueAction(std::move(download_action)); processor_->EnqueueAction(std::move(filesystem_verifier_action)); @@ -731,11 +838,15 @@ void UpdateAttempterAndroid::CollectAndReportUpdateMetricsOnUpdateFinished( total_bytes_downloaded; int download_overhead_percentage = 0; - if (current_bytes_downloaded > 0) { + if (total_bytes_downloaded >= payload_size) { + CHECK_GT(payload_size, 0); download_overhead_percentage = - (total_bytes_downloaded - current_bytes_downloaded) * 100ull / - current_bytes_downloaded; + (total_bytes_downloaded - payload_size) * 100ull / payload_size; + } else { + LOG(WARNING) << "Downloaded bytes " << total_bytes_downloaded + << " is smaller than the payload size " << payload_size; } + metrics_reporter_->ReportSuccessfulUpdateMetrics( static_cast<int>(attempt_number), 0, // update abandoned count @@ -826,10 +937,118 @@ void UpdateAttempterAndroid::ClearMetricsPrefs() { CHECK(prefs_); prefs_->Delete(kPrefsCurrentBytesDownloaded); prefs_->Delete(kPrefsNumReboots); - prefs_->Delete(kPrefsPayloadAttemptNumber); prefs_->Delete(kPrefsSystemUpdatedMarker); prefs_->Delete(kPrefsUpdateTimestampStart); prefs_->Delete(kPrefsUpdateBootTimestampStart); } +BootControlInterface::Slot UpdateAttempterAndroid::GetCurrentSlot() const { + return boot_control_->GetCurrentSlot(); +} + +BootControlInterface::Slot UpdateAttempterAndroid::GetTargetSlot() const { + return GetCurrentSlot() == 0 ? 1 : 0; +} + +uint64_t UpdateAttempterAndroid::AllocateSpaceForPayload( + const std::string& metadata_filename, + const vector<string>& key_value_pair_headers, + brillo::ErrorPtr* error) { + DeltaArchiveManifest manifest; + if (!VerifyPayloadParseManifest(metadata_filename, &manifest, error)) { + return 0; + } + std::map<string, string> headers; + if (!ParseKeyValuePairHeaders(key_value_pair_headers, &headers, error)) { + return 0; + } + + string payload_id = GetPayloadId(headers); + uint64_t required_size = 0; + if (!DeltaPerformer::PreparePartitionsForUpdate(prefs_, + boot_control_, + GetTargetSlot(), + manifest, + payload_id, + &required_size)) { + if (required_size == 0) { + LogAndSetError(error, FROM_HERE, "Failed to allocate space for payload."); + return 0; + } else { + LOG(ERROR) << "Insufficient space for payload: " << required_size + << " bytes"; + return required_size; + } + } + + LOG(INFO) << "Successfully allocated space for payload."; + return 0; +} + +void UpdateAttempterAndroid::CleanupSuccessfulUpdate( + std::unique_ptr<CleanupSuccessfulUpdateCallbackInterface> callback, + brillo::ErrorPtr* error) { + if (cleanup_previous_update_code_.has_value()) { + LOG(INFO) << "CleanupSuccessfulUpdate has previously completed with " + << utils::ErrorCodeToString(*cleanup_previous_update_code_); + if (callback) { + callback->OnCleanupComplete( + static_cast<int32_t>(*cleanup_previous_update_code_)); + } + return; + } + if (callback) { + auto callback_ptr = callback.get(); + cleanup_previous_update_callbacks_.emplace_back(std::move(callback)); + callback_ptr->RegisterForDeathNotifications( + base::Bind(&UpdateAttempterAndroid::RemoveCleanupPreviousUpdateCallback, + base::Unretained(this), + base::Unretained(callback_ptr))); + } + ScheduleCleanupPreviousUpdate(); +} + +void UpdateAttempterAndroid::ScheduleCleanupPreviousUpdate() { + // If a previous CleanupSuccessfulUpdate call has not finished, or an update + // is in progress, skip enqueueing the action. + if (processor_->IsRunning()) { + LOG(INFO) << "Already processing an update. CleanupPreviousUpdate should " + << "be done when the current update finishes."; + return; + } + LOG(INFO) << "Scheduling CleanupPreviousUpdateAction."; + auto action = + boot_control_->GetDynamicPartitionControl() + ->GetCleanupPreviousUpdateAction(boot_control_, prefs_, this); + processor_->EnqueueAction(std::move(action)); + processor_->set_delegate(this); + SetStatusAndNotify(UpdateStatus::CLEANUP_PREVIOUS_UPDATE); + processor_->StartProcessing(); +} + +void UpdateAttempterAndroid::OnCleanupProgressUpdate(double progress) { + for (auto&& callback : cleanup_previous_update_callbacks_) { + callback->OnCleanupProgressUpdate(progress); + } +} + +void UpdateAttempterAndroid::NotifyCleanupPreviousUpdateCallbacksAndClear() { + CHECK(cleanup_previous_update_code_.has_value()); + for (auto&& callback : cleanup_previous_update_callbacks_) { + callback->OnCleanupComplete( + static_cast<int32_t>(*cleanup_previous_update_code_)); + } + cleanup_previous_update_callbacks_.clear(); +} + +void UpdateAttempterAndroid::RemoveCleanupPreviousUpdateCallback( + CleanupSuccessfulUpdateCallbackInterface* callback) { + auto end_it = + std::remove_if(cleanup_previous_update_callbacks_.begin(), + cleanup_previous_update_callbacks_.end(), + [&](const auto& e) { return e.get() == callback; }); + cleanup_previous_update_callbacks_.erase( + end_it, cleanup_previous_update_callbacks_.end()); +} + } // namespace chromeos_update_engine diff --git a/update_attempter_android.h b/update_attempter_android.h index e4b40ded..55003a09 100644 --- a/update_attempter_android.h +++ b/update_attempter_android.h @@ -23,6 +23,7 @@ #include <string> #include <vector> +#include <android-base/unique_fd.h> #include <base/time/time.h> #include "update_engine/client_library/include/update_engine/update_status.h" @@ -36,6 +37,7 @@ #include "update_engine/metrics_utils.h" #include "update_engine/network_selector_interface.h" #include "update_engine/payload_consumer/download_action.h" +#include "update_engine/payload_consumer/filesystem_verifier_action.h" #include "update_engine/payload_consumer/postinstall_runner_action.h" #include "update_engine/service_delegate_android_interface.h" #include "update_engine/service_observer_interface.h" @@ -46,7 +48,9 @@ class UpdateAttempterAndroid : public ServiceDelegateAndroidInterface, public ActionProcessorDelegate, public DownloadActionDelegate, - public PostinstallRunnerAction::DelegateInterface { + public FilesystemVerifyDelegate, + public PostinstallRunnerAction::DelegateInterface, + public CleanupPreviousUpdateActionDelegateInterface { public: using UpdateStatus = update_engine::UpdateStatus; @@ -65,12 +69,24 @@ class UpdateAttempterAndroid int64_t payload_size, const std::vector<std::string>& key_value_pair_headers, brillo::ErrorPtr* error) override; + bool ApplyPayload(int fd, + int64_t payload_offset, + int64_t payload_size, + const std::vector<std::string>& key_value_pair_headers, + brillo::ErrorPtr* error) override; bool SuspendUpdate(brillo::ErrorPtr* error) override; bool ResumeUpdate(brillo::ErrorPtr* error) override; bool CancelUpdate(brillo::ErrorPtr* error) override; bool ResetStatus(brillo::ErrorPtr* error) override; bool VerifyPayloadApplicable(const std::string& metadata_filename, brillo::ErrorPtr* error) override; + uint64_t AllocateSpaceForPayload( + const std::string& metadata_filename, + const std::vector<std::string>& key_value_pair_headers, + brillo::ErrorPtr* error) override; + void CleanupSuccessfulUpdate( + std::unique_ptr<CleanupSuccessfulUpdateCallbackInterface> callback, + brillo::ErrorPtr* error) override; // ActionProcessorDelegate methods: void ProcessingDone(const ActionProcessor* processor, @@ -87,9 +103,15 @@ class UpdateAttempterAndroid bool ShouldCancel(ErrorCode* cancel_reason) override; void DownloadComplete() override; + // FilesystemVerifyDelegate overrides + void OnVerifyProgressUpdate(double progress) override; + // PostinstallRunnerAction::DelegateInterface void ProgressUpdate(double progress) override; + // CleanupPreviousUpdateActionDelegateInterface + void OnCleanupProgressUpdate(double progress) override; + private: friend class UpdateAttempterAndroidTest; @@ -151,11 +173,31 @@ class UpdateAttempterAndroid void UpdatePrefsOnUpdateStart(bool is_resume); // Prefs to delete: - // |kPrefsNumReboots|, |kPrefsPayloadAttemptNumber|, + // |kPrefsNumReboots|, |kPrefsCurrentBytesDownloaded| // |kPrefsSystemUpdatedMarker|, |kPrefsUpdateTimestampStart|, - // |kPrefsUpdateBootTimestampStart|, |kPrefsCurrentBytesDownloaded| + // |kPrefsUpdateBootTimestampStart| void ClearMetricsPrefs(); + // Return source and target slots for update. + BootControlInterface::Slot GetCurrentSlot() const; + BootControlInterface::Slot GetTargetSlot() const; + + // Helper of public VerifyPayloadApplicable. Return the parsed manifest in + // |manifest|. + static bool VerifyPayloadParseManifest(const std::string& metadata_filename, + DeltaArchiveManifest* manifest, + brillo::ErrorPtr* error); + + // Enqueue and run a CleanupPreviousUpdateAction. + void ScheduleCleanupPreviousUpdate(); + + // Notify and clear |cleanup_previous_update_callbacks_|. + void NotifyCleanupPreviousUpdateCallbacksAndClear(); + + // Remove |callback| from |cleanup_previous_update_callbacks_|. + void RemoveCleanupPreviousUpdateCallback( + CleanupSuccessfulUpdateCallbackInterface* callback); + DaemonStateInterface* daemon_state_; // DaemonStateAndroid pointers. @@ -191,6 +233,14 @@ class UpdateAttempterAndroid std::unique_ptr<MetricsReporterInterface> metrics_reporter_; + ::android::base::unique_fd payload_fd_; + + std::vector<std::unique_ptr<CleanupSuccessfulUpdateCallbackInterface>> + cleanup_previous_update_callbacks_; + // Result of previous CleanupPreviousUpdateAction. Nullopt If + // CleanupPreviousUpdateAction has not been executed. + std::optional<ErrorCode> cleanup_previous_update_code_{std::nullopt}; + DISALLOW_COPY_AND_ASSIGN(UpdateAttempterAndroid); }; diff --git a/update_attempter_android_unittest.cc b/update_attempter_android_unittest.cc index 2593d44a..721b7352 100644 --- a/update_attempter_android_unittest.cc +++ b/update_attempter_android_unittest.cc @@ -18,6 +18,7 @@ #include <memory> #include <string> +#include <utility> #include <android-base/properties.h> #include <base/time/time.h> @@ -57,6 +58,11 @@ class UpdateAttempterAndroidTest : public ::testing::Test { update_attempter_android_.status_ = status; } + void AddPayload(InstallPlan::Payload&& payload) { + update_attempter_android_.install_plan_.payloads.push_back( + std::move(payload)); + } + UpdateAttempterAndroid update_attempter_android_{ &daemon_state_, &prefs_, &boot_control_, &hardware_}; @@ -111,9 +117,10 @@ TEST_F(UpdateAttempterAndroidTest, UpdatePrefsBuildVersionChangeOnInit) { update_attempter_android_.Init(); // Check that we reset the metric prefs. EXPECT_FALSE(prefs_.Exists(kPrefsNumReboots)); - EXPECT_FALSE(prefs_.Exists(kPrefsPayloadAttemptNumber)); EXPECT_FALSE(prefs_.Exists(kPrefsUpdateTimestampStart)); EXPECT_FALSE(prefs_.Exists(kPrefsSystemUpdatedMarker)); + // PayloadAttemptNumber should persist across reboots. + EXPECT_TRUE(prefs_.Exists(kPrefsPayloadAttemptNumber)); } TEST_F(UpdateAttempterAndroidTest, ReportMetricsOnUpdateTerminated) { @@ -142,9 +149,13 @@ TEST_F(UpdateAttempterAndroidTest, ReportMetricsOnUpdateTerminated) { .Times(1); EXPECT_CALL(*metrics_reporter_, ReportSuccessfulUpdateMetrics( - 2, 0, _, _, _, _, duration, duration_uptime, 3, _)) + 2, 0, _, 50, _, _, duration, duration_uptime, 3, _)) .Times(1); + // Adds a payload of 50 bytes to the InstallPlan. + InstallPlan::Payload payload; + payload.size = 50; + AddPayload(std::move(payload)); SetUpdateStatus(UpdateStatus::UPDATE_AVAILABLE); update_attempter_android_.ProcessingDone(nullptr, ErrorCode::kSuccess); @@ -178,15 +189,20 @@ TEST_F(UpdateAttempterAndroidTest, ReportMetricsForBytesDownloaded) { _, _, _, - _, + 50, test_utils::DownloadSourceMatcher(total_bytes), - 125, + 80, _, _, _, _)) .Times(1); + // Adds a payload of 50 bytes to the InstallPlan. + InstallPlan::Payload payload; + payload.size = 50; + AddPayload(std::move(payload)); + // The first update fails after receiving 50 bytes in total. update_attempter_android_.BytesReceived(30, 50, 200); update_attempter_android_.ProcessingDone(nullptr, ErrorCode::kError); @@ -198,7 +214,7 @@ TEST_F(UpdateAttempterAndroidTest, ReportMetricsForBytesDownloaded) { metrics_utils::GetPersistedValue(kPrefsTotalBytesDownloaded, &prefs_)); // The second update succeeds after receiving 40 bytes, which leads to a - // overhead of 50 / 40 = 125%. + // overhead of (90 - 50) / 50 = 80%. update_attempter_android_.BytesReceived(40, 40, 50); update_attempter_android_.ProcessingDone(nullptr, ErrorCode::kSuccess); // Both prefs should be cleared. diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc index 7466aba5..354416eb 100644 --- a/update_attempter_unittest.cc +++ b/update_attempter_unittest.cc @@ -665,7 +665,8 @@ TEST_F(UpdateAttempterTest, GetErrorCodeForActionTest) { EXPECT_EQ( ErrorCode::kOmahaResponseHandlerError, GetErrorCodeForAction(&omaha_response_handler_action, ErrorCode::kError)); - FilesystemVerifierAction filesystem_verifier_action; + DynamicPartitionControlStub dynamic_control_stub; + FilesystemVerifierAction filesystem_verifier_action(&dynamic_control_stub); EXPECT_EQ( ErrorCode::kFilesystemVerifierError, GetErrorCodeForAction(&filesystem_verifier_action, ErrorCode::kError)); diff --git a/update_boot_flags_action.cc b/update_boot_flags_action.cc index 97ef7f23..ee92ae0a 100644 --- a/update_boot_flags_action.cc +++ b/update_boot_flags_action.cc @@ -50,8 +50,11 @@ void UpdateBootFlagsAction::PerformAction() { } } -void UpdateBootFlagsAction::CompleteUpdateBootFlags(bool successful) { +void UpdateBootFlagsAction::TerminateProcessing() { is_running_ = false; +} + +void UpdateBootFlagsAction::CompleteUpdateBootFlags(bool successful) { if (!successful) { // We ignore the failure for now because if the updating boot flags is flaky // or has a bug in a specific release, then blocking the update can cause @@ -61,6 +64,18 @@ void UpdateBootFlagsAction::CompleteUpdateBootFlags(bool successful) { // TODO(ahassani): Add new error code metric for kUpdateBootFlagsFailed. LOG(ERROR) << "Updating boot flags failed, but ignoring its failure."; } + + // As the callback to MarkBootSuccessfulAsync, this function can still be + // called even after the current UpdateBootFlagsAction object get destroyed by + // the action processor. In this case, check the value of the static variable + // |is_running_| and skip executing the callback function. + if (!is_running_) { + LOG(INFO) << "UpdateBootFlagsAction is no longer running."; + return; + } + + is_running_ = false; + updated_boot_flags_ = true; processor_->ActionComplete(this, ErrorCode::kSuccess); } diff --git a/update_boot_flags_action.h b/update_boot_flags_action.h index afa2c3f1..892aab7b 100644 --- a/update_boot_flags_action.h +++ b/update_boot_flags_action.h @@ -30,6 +30,8 @@ class UpdateBootFlagsAction : public AbstractAction { void PerformAction() override; + void TerminateProcessing() override; + static std::string StaticType() { return "UpdateBootFlagsAction"; } std::string Type() const override { return StaticType(); } diff --git a/update_engine.conf b/update_engine.conf index af213ad9..b6ca3c47 100644 --- a/update_engine.conf +++ b/update_engine.conf @@ -1,2 +1,2 @@ PAYLOAD_MAJOR_VERSION=2 -PAYLOAD_MINOR_VERSION=6 +PAYLOAD_MINOR_VERSION=7 diff --git a/update_engine.rc b/update_engine.rc index a7d62357..b9f80fc1 100644 --- a/update_engine.rc +++ b/update_engine.rc @@ -1,8 +1,8 @@ service update_engine /system/bin/update_engine --logtostderr --logtofile --foreground class late_start user root - group root system wakelock inet cache - writepid /dev/cpuset/system-background/tasks + group root system wakelock inet cache media_rw + writepid /dev/cpuset/system-background/tasks /dev/blkio/background/tasks disabled on property:ro.boot.slot_suffix=* diff --git a/update_engine_client_android.cc b/update_engine_client_android.cc index 6863799f..1a68cf49 100644 --- a/update_engine_client_android.cc +++ b/update_engine_client_android.cc @@ -72,12 +72,15 @@ class UpdateEngineClientAndroid : public brillo::Daemon { // Called whenever the UpdateEngine daemon dies. void UpdateEngineServiceDied(); + static std::vector<android::String16> ParseHeaders(const std::string& arg); + // Copy of argc and argv passed to main(). int argc_; char** argv_; android::sp<android::os::IUpdateEngine> service_; android::sp<android::os::BnUpdateEngineCallback> callback_; + android::sp<android::os::BnUpdateEngineCallback> cleanup_callback_; brillo::BinderWatcher binder_watcher_; }; @@ -123,15 +126,16 @@ int UpdateEngineClientAndroid::OnInit() { DEFINE_string(headers, "", "A list of key-value pairs, one element of the list per line. " - "Used when --update is passed."); + "Used when --update or --allocate is passed."); DEFINE_bool(verify, false, "Given payload metadata, verify if the payload is applicable."); + DEFINE_bool(allocate, false, "Given payload metadata, allocate space."); DEFINE_string(metadata, "/data/ota_package/metadata", "The path to the update payload metadata. " - "Used when --verify is passed."); + "Used when --verify or --allocate is passed."); DEFINE_bool(suspend, false, "Suspend an ongoing update and exit."); DEFINE_bool(resume, false, "Resume a suspended update."); @@ -141,7 +145,10 @@ int UpdateEngineClientAndroid::OnInit() { false, "Follow status update changes until a final state is reached. " "Exit status is 0 if the update succeeded, and 1 otherwise."); - + DEFINE_bool(merge, + false, + "Wait for previous update to merge. " + "Only available after rebooting to new slot."); // Boilerplate init commands. base::CommandLine::Init(argc_, argv_); brillo::FlagHelper::Init(argc_, argv_, "Android Update Engine Client"); @@ -200,6 +207,36 @@ int UpdateEngineClientAndroid::OnInit() { return ExitWhenIdle(status); } + if (FLAGS_allocate) { + auto headers = ParseHeaders(FLAGS_headers); + int64_t ret = 0; + Status status = service_->allocateSpaceForPayload( + android::String16{FLAGS_metadata.data(), FLAGS_metadata.size()}, + headers, + &ret); + if (status.isOk()) { + if (ret == 0) { + LOG(INFO) << "Successfully allocated space for payload."; + } else { + LOG(INFO) << "Insufficient space; required " << ret << " bytes."; + } + } else { + LOG(INFO) << "Allocation failed."; + } + return ExitWhenIdle(status); + } + + if (FLAGS_merge) { + // Register a callback object with the service. + cleanup_callback_ = new UECallback(this); + Status status = service_->cleanupSuccessfulUpdate(cleanup_callback_); + if (!status.isOk()) { + LOG(ERROR) << "Failed to call cleanupSuccessfulUpdate."; + return ExitWhenIdle(status); + } + keep_running = true; + } + if (FLAGS_follow) { // Register a callback object with the service. callback_ = new UECallback(this); @@ -212,12 +249,7 @@ int UpdateEngineClientAndroid::OnInit() { } if (FLAGS_update) { - std::vector<std::string> headers = base::SplitString( - FLAGS_headers, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); - std::vector<android::String16> and_headers; - for (const auto& header : headers) { - and_headers.push_back(android::String16{header.data(), header.size()}); - } + auto and_headers = ParseHeaders(FLAGS_headers); Status status = service_->applyPayload( android::String16{FLAGS_payload.data(), FLAGS_payload.size()}, FLAGS_offset, @@ -261,6 +293,17 @@ void UpdateEngineClientAndroid::UpdateEngineServiceDied() { QuitWithExitCode(1); } +std::vector<android::String16> UpdateEngineClientAndroid::ParseHeaders( + const std::string& arg) { + std::vector<std::string> headers = base::SplitString( + arg, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + std::vector<android::String16> and_headers; + for (const auto& header : headers) { + and_headers.push_back(android::String16{header.data(), header.size()}); + } + return and_headers; +} + } // namespace internal } // namespace chromeos_update_engine diff --git a/update_manager/boxed_value.cc b/update_manager/boxed_value.cc index ee7236cf..ba84a414 100644 --- a/update_manager/boxed_value.cc +++ b/update_manager/boxed_value.cc @@ -178,6 +178,8 @@ string BoxedValue::ValuePrinter<Stage>(const void* value) { return "Reporting Error Event"; case Stage::kAttemptingRollback: return "Attempting Rollback"; + case Stage::kCleanupPreviousUpdate: + return "Cleanup Previous Update"; } NOTREACHED(); return "Unknown"; diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc index a4926f43..85cc3ae9 100644 --- a/update_manager/chromeos_policy.cc +++ b/update_manager/chromeos_policy.cc @@ -154,6 +154,8 @@ bool HandleErrorCode(ErrorCode err_code, int* url_num_error_p) { case ErrorCode::kInternalLibCurlError: case ErrorCode::kUnresolvedHostError: case ErrorCode::kUnresolvedHostRecovered: + case ErrorCode::kNotEnoughSpace: + case ErrorCode::kDeviceCorrupted: case ErrorCode::kPackageExcludedFromUpdate: LOG(INFO) << "Not changing URL index or failure count due to error " << chromeos_update_engine::utils::ErrorCodeToString(err_code) @@ -598,7 +600,6 @@ EvalStatus ChromeOSPolicy::UpdateBackoffAndDownloadUrl( string* error, UpdateBackoffAndDownloadUrlResult* result, const UpdateState& update_state) const { - // Sanity checks. DCHECK_GE(update_state.download_errors_max, 0); // Set default result values. @@ -670,7 +671,7 @@ EvalStatus ChromeOSPolicy::UpdateBackoffAndDownloadUrl( Time prev_err_time; bool is_first = true; for (const auto& err_tuple : update_state.download_errors) { - // Do some sanity checks. + // Do some validation checks. int used_url_idx = get<0>(err_tuple); if (is_first && url_idx >= 0 && used_url_idx != url_idx) { LOG(WARNING) << "First URL in error log (" << used_url_idx diff --git a/update_manager/real_updater_provider.cc b/update_manager/real_updater_provider.cc index 84657180..1548d574 100644 --- a/update_manager/real_updater_provider.cc +++ b/update_manager/real_updater_provider.cc @@ -170,6 +170,8 @@ const StageVariable::CurrOpStrToStage StageVariable::curr_op_str_to_stage[] = { Stage::kReportingErrorEvent}, {update_engine::kUpdateStatusAttemptingRollback, Stage::kAttemptingRollback}, + {update_engine::kUpdateStatusCleanupPreviousUpdate, + Stage::kCleanupPreviousUpdate}, }; const Stage* StageVariable::GetValue(TimeDelta /* timeout */, string* errmsg) { diff --git a/update_manager/updater_provider.h b/update_manager/updater_provider.h index 98fd6d14..86af1c8a 100644 --- a/update_manager/updater_provider.h +++ b/update_manager/updater_provider.h @@ -36,6 +36,7 @@ enum class Stage { kUpdatedNeedReboot, kReportingErrorEvent, kAttemptingRollback, + kCleanupPreviousUpdate, }; enum class UpdateRequestStatus { diff --git a/update_metadata.proto b/update_metadata.proto index 3d136cad..373ee5ef 100644 --- a/update_metadata.proto +++ b/update_metadata.proto @@ -14,24 +14,26 @@ // limitations under the License. // -// Update file format: A delta update file contains all the deltas needed -// to update a system from one specific version to another specific -// version. The update format is represented by this struct pseudocode: +// Update file format: An update file contains all the operations needed +// to update a system to a specific version. It can be a full payload which +// can update from any version, or a delta payload which can only update +// from a specific version. +// The update format is represented by this struct pseudocode: // struct delta_update_file { // char magic[4] = "CrAU"; -// uint64 file_format_version; +// uint64 file_format_version; // payload major version // uint64 manifest_size; // Size of protobuf DeltaArchiveManifest // -// // Only present if format_version > 1: +// // Only present if format_version >= 2: // uint32 metadata_signature_size; // -// // The Bzip2 compressed DeltaArchiveManifest -// char manifest[]; +// // The DeltaArchiveManifest protobuf serialized, not compressed. +// char manifest[manifest_size]; // // // The signature of the metadata (from the beginning of the payload up to // // this location, not including the signature itself). This is a serialized // // Signatures message. -// char medatada_signature_message[metadata_signature_size]; +// char metadata_signature_message[metadata_signature_size]; // // // Data blobs for files, no specific format. The specific offset // // and length of each data blob is recorded in the DeltaArchiveManifest. @@ -39,9 +41,12 @@ // char data[]; // } blobs[]; // -// // These two are not signed: +// // The signature of the entire payload, everything up to this location, +// // except that metadata_signature_message is skipped to simplify signing +// // process. These two are not signed: // uint64 payload_signatures_message_size; -// char payload_signatures_message[]; +// // This is a serialized Signatures message. +// char payload_signatures_message[payload_signatures_message_size]; // // }; @@ -61,13 +66,13 @@ // dst_extents on the drive, zero padding to block size. // - MOVE: Copy the data in src_extents to dst_extents. Extents may overlap, // so it may be desirable to read all src_extents data into memory before -// writing it out. +// writing it out. (deprecated) // - SOURCE_COPY: Copy the data in src_extents in the old partition to // dst_extents in the new partition. There's no overlapping of data because // the extents are in different partitions. // - BSDIFF: Read src_length bytes from src_extents into memory, perform // bspatch with attached data, write new data to dst_extents, zero padding -// to block size. +// to block size. (deprecated) // - SOURCE_BSDIFF: Read the data in src_extents in the old partition, perform // bspatch with the attached data and write the new data to dst_extents in the // new partition. @@ -102,6 +107,11 @@ option optimize_for = LITE_RUNTIME; // A sentinel value (kuint64max) as the start block denotes a sparse-hole // in a file whose block-length is specified by num_blocks. +message Extent { + optional uint64 start_block = 1; + optional uint64 num_blocks = 2; +} + // Signatures: Updates may be signed by the OS vendor. The client verifies // an update's signature by hashing the entire download. The section of the // download that contains the signature is at the end of the file, so when @@ -114,15 +124,19 @@ option optimize_for = LITE_RUNTIME; // to verify the download. The public key is expected to be part of the // client. -message Extent { - optional uint64 start_block = 1; - optional uint64 num_blocks = 2; -} - message Signatures { message Signature { - optional uint32 version = 1; + optional uint32 version = 1 [deprecated = true]; optional bytes data = 2; + + // The DER encoded signature size of EC keys is nondeterministic for + // different input of sha256 hash. However, we need the size of the + // serialized signatures protobuf string to be fixed before signing; + // because this size is part of the content to be signed. Therefore, we + // always pad the signature data to the maximum possible signature size of + // a given key. And the payload verifier will truncate the signature to + // its correct size based on the value of |unpadded_signature_size|. + optional fixed32 unpadded_signature_size = 3; } repeated Signature signatures = 1; } @@ -211,6 +225,22 @@ message InstallOperation { optional bytes src_sha256_hash = 9; } +// Hints to VAB snapshot to skip writing some blocks if these blocks are +// identical to the ones on the source image. The src & dst extents for each +// CowMergeOperation should be contiguous, and they're a subset of an OTA +// InstallOperation. +// During merge time, we need to follow the pre-computed sequence to avoid +// read after write, similar to the inplace update schema. +message CowMergeOperation { + enum Type { + COW_COPY = 0; // identical blocks + } + optional Type type = 1; + + optional Extent src_extent = 2; + optional Extent dst_extent = 3; +} + // Describes the update to apply to a single partition. message PartitionUpdate { // A platform-specific name to identify the partition set being updated. For @@ -274,6 +304,16 @@ message PartitionUpdate { // The number of FEC roots. optional uint32 fec_roots = 16 [default = 2]; + + // Per-partition version used for downgrade detection, added + // as an effort to support partial updates. For most partitions, + // this is the build timestamp. + optional string version = 17; + + // A sorted list of CowMergeOperation. When writing cow, we can choose to + // skip writing the raw bytes for these extents. During snapshot merge, the + // bytes will read from the source partitions instead. + repeated CowMergeOperation merge_operations = 18; } message DynamicPartitionGroup { @@ -290,13 +330,19 @@ message DynamicPartitionGroup { // Metadata related to all dynamic partitions. message DynamicPartitionMetadata { - // All updateable groups present in |partitions| of this DeltaArchiveManifest. + // All updatable groups present in |partitions| of this DeltaArchiveManifest. // - If an updatable group is on the device but not in the manifest, it is // not updated. Hence, the group will not be resized, and partitions cannot // be added to or removed from the group. // - If an updatable group is in the manifest but not on the device, the group // is added to the device. repeated DynamicPartitionGroup groups = 1; + + // Whether dynamic partitions have snapshots during the update. If this is + // set to true, the update_engine daemon creates snapshots for all dynamic + // partitions if possible. If this is unset, the update_engine daemon MUST + // NOT create snapshots for dynamic partitions. + optional bool snapshot_enabled = 2; } message DeltaArchiveManifest { @@ -330,6 +376,7 @@ message DeltaArchiveManifest { optional ImageInfo new_image_info = 11; // The minor version, also referred as "delta version", of the payload. + // Minor version 0 is full payload, everything else is delta payload. optional uint32 minor_version = 12 [default = 0]; // Only present in major version >= 2. List of partitions that will be @@ -346,4 +393,7 @@ message DeltaArchiveManifest { // Metadata related to all dynamic partitions. optional DynamicPartitionMetadata dynamic_partition_metadata = 15; + + // If the payload only updates a subset of partitions on the device. + optional bool partial_update = 16; } diff --git a/update_status_utils.cc b/update_status_utils.cc index 6c618eca..a702c61a 100644 --- a/update_status_utils.cc +++ b/update_status_utils.cc @@ -66,6 +66,8 @@ const char* UpdateStatusToString(const UpdateStatus& status) { return update_engine::kUpdateStatusAttemptingRollback; case UpdateStatus::DISABLED: return update_engine::kUpdateStatusDisabled; + case UpdateStatus::CLEANUP_PREVIOUS_UPDATE: + return update_engine::kUpdateStatusCleanupPreviousUpdate; } NOTREACHED(); |