diff options
516 files changed, 30298 insertions, 21078 deletions
diff --git a/.clang-format b/.clang-format index 3b6a6276..3044f598 100644 --- a/.clang-format +++ b/.clang-format @@ -37,3 +37,7 @@ DerivePointerAlignment: false IncludeBlocks: Preserve PointerAlignment: Left TabWidth: 2 + +# cpplint.py does smarter #include sorting than clang-format (the former ignores +# case and changes '-' to '_'). +SortIncludes: false @@ -15,6 +15,23 @@ // // AIDL interface between libupdate_engine and framework.jar +package { + default_applicable_licenses: ["system_update_engine_license"], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "system_update_engine_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "NOTICE", + ], +} + filegroup { name: "libupdate_engine_aidl", srcs: [ @@ -29,12 +46,7 @@ cc_defaults { cflags: [ "-DBASE_VER=576279", - "-DUSE_BINDER=1", - "-DUSE_CHROME_NETWORK_PROXY=0", - "-DUSE_CHROME_KIOSK_APP=0", "-DUSE_HWID_OVERRIDE=0", - "-DUSE_MTD=0", - "-DUSE_OMAHA=0", "-D_FILE_OFFSET_BITS=64", "-D_POSIX_C_SOURCE=199309L", "-Wa,--noexecstack", @@ -54,7 +66,7 @@ cc_defaults { ], include_dirs: ["system"], local_include_dirs: ["client_library/include"], - static_libs: ["libgtest_prod"], + header_libs: ["libgtest_prod_headers"], shared_libs: [ "libbrillo-stream", "libbrillo", @@ -85,6 +97,44 @@ cc_defaults { }, } +// libcow_operation_convert (type: library) +// ======================================================== +cc_library_static { + name: "libpayload_extent_utils", + defaults: [ + "ue_defaults", + ], + host_supported: true, + recovery_available: true, + srcs: [ + "payload_generator/extent_utils.cc", + ], + static_libs: [ + "update_metadata-protos", + ], +} + +cc_library { + name: "libcow_operation_convert", + host_supported: true, + recovery_available: true, + defaults: [ + "ue_defaults", + "update_metadata-protos_exports", + ], + srcs: [ + "common/cow_operation_convert.cc", + ], + static_libs: [ + "libsnapshot_cow", + "update_metadata-protos", + "libpayload_extent_ranges", + "libpayload_extent_utils", + "libbrotli", + "libz", + ], +} + // update_metadata-protos (type: static_library) // ======================================================== // Protobufs. @@ -123,15 +173,22 @@ cc_defaults { "libbz", "libbspatch", "libbrotli", + "libc++fs", "libfec_rs", "libpuffpatch", "libverity_tree", + "libsnapshot_cow", + "libbrotli", + "libz", + "libpayload_extent_ranges", + "libpayload_extent_utils", + "libcow_operation_convert", ], shared_libs: [ - "libziparchive", "libbase", "libcrypto", "libfec", + "libziparchive", ], } @@ -145,6 +202,7 @@ cc_library_static { recovery_available: true, srcs: [ + "aosp/platform_constants_android.cc", "common/action_processor.cc", "common/boot_control_stub.cc", "common/clock.cc", @@ -158,7 +216,6 @@ cc_library_static { "common/http_fetcher.cc", "common/hwid_override.cc", "common/multi_range_http_fetcher.cc", - "common/platform_constants_android.cc", "common/prefs.cc", "common/proxy_resolver.cc", "common/subprocess.cc", @@ -167,8 +224,8 @@ cc_library_static { "payload_consumer/bzip_extent_writer.cc", "payload_consumer/cached_file_descriptor.cc", "payload_consumer/certificate_parser_android.cc", + "payload_consumer/cow_writer_file_descriptor.cc", "payload_consumer/delta_performer.cc", - "payload_consumer/download_action.cc", "payload_consumer/extent_reader.cc", "payload_consumer/extent_writer.cc", "payload_consumer/file_descriptor.cc", @@ -180,10 +237,15 @@ cc_library_static { "payload_consumer/payload_constants.cc", "payload_consumer/payload_metadata.cc", "payload_consumer/payload_verifier.cc", + "payload_consumer/partition_writer.cc", + "payload_consumer/partition_writer_factory_android.cc", + "payload_consumer/vabc_partition_writer.cc", + "payload_consumer/snapshot_extent_writer.cc", "payload_consumer/postinstall_runner_action.cc", "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", ], } @@ -200,6 +262,8 @@ cc_defaults { "libgsi", "libpayload_consumer", "libsnapshot", + "libsnapshot_cow", + "libz", "update_metadata-protos", ], shared_libs: [ @@ -241,10 +305,10 @@ cc_library_static { recovery_available: true, srcs: [ - "boot_control_android.cc", - "cleanup_previous_update_action.cc", - "dynamic_partition_control_android.cc", - "dynamic_partition_utils.cc", + "aosp/boot_control_android.cc", + "aosp/cleanup_previous_update_action.cc", + "aosp/dynamic_partition_control_android.cc", + "aosp/dynamic_partition_utils.cc", ], } @@ -261,10 +325,15 @@ cc_defaults { ], static_libs: [ + "libavb", + "libavb_user", + "gkiprops", "libpayload_consumer", "libupdate_engine_boot_control", + "PlatformProperties", ], shared_libs: [ + "apex_aidl_interface-cpp", "libandroid_net", "libbase", "libbinder", @@ -273,11 +342,15 @@ cc_defaults { "libbrillo-binder", "libcurl", "libcutils", + "libupdate_engine_stable-V1-cpp", "liblog", "libssl", "libstatslog", "libutils", ], + whole_static_libs: [ + "com.android.sysprop.apex", + ], } cc_library_static { @@ -298,17 +371,20 @@ cc_library_static { srcs: [ ":libupdate_engine_aidl", - "binder_service_android.cc", + "common/system_state.cc", + "aosp/apex_handler_android.cc", + "aosp/binder_service_android.cc", + "aosp/binder_service_stable_android.cc", + "aosp/daemon_android.cc", + "aosp/daemon_state_android.cc", + "aosp/hardware_android.cc", + "aosp/logging_android.cc", + "aosp/network_selector_android.cc", + "aosp/update_attempter_android.cc", "certificate_checker.cc", - "daemon.cc", - "daemon_state_android.cc", - "hardware_android.cc", + "download_action.cc", "libcurl_http_fetcher.cc", - "logging_android.cc", - "metrics_reporter_android.cc", "metrics_utils.cc", - "network_selector_android.cc", - "update_attempter_android.cc", "update_boot_flags_action.cc", "update_status_utils.cc", ], @@ -330,7 +406,7 @@ cc_binary { "otacerts", ], - srcs: ["main.cc"], + srcs: ["main.cc", "aosp/metrics_reporter_android.cc"], init_rc: ["update_engine.rc"], } @@ -353,15 +429,18 @@ cc_binary { // TODO(deymo): Remove external/cros/system_api/dbus once the strings are moved // out of the DBus interface. include_dirs: ["external/cros/system_api/dbus"], + header_libs: ["libgtest_prod_headers"], srcs: [ - "hardware_android.cc", - "logging_android.cc", - "metrics_reporter_stub.cc", + "aosp/hardware_android.cc", + "aosp/logging_android.cc", + "aosp/sideload_main.cc", + "aosp/update_attempter_android.cc", + "common/metrics_reporter_stub.cc", + "common/network_selector_stub.cc", + "common/system_state.cc", + "download_action.cc", "metrics_utils.cc", - "network_selector_stub.cc", - "sideload_main.cc", - "update_attempter_android.cc", "update_boot_flags_action.cc", "update_status_utils.cc", ], @@ -382,9 +461,9 @@ cc_binary { // We add the static versions of the shared libraries that are not installed to // recovery image due to size concerns. Need to include all the static library // dependencies of these static libraries. + "gkiprops", "libevent", "libmodpb64", - "libgtest_prod", "libprotobuf-cpp-lite", "libbrillo-stream", "libbrillo", @@ -406,54 +485,6 @@ cc_binary { ], } -// libupdate_engine_client (type: shared_library) -// ======================================================== -cc_library_shared { - name: "libupdate_engine_client", - - cflags: [ - "-Wall", - "-Werror", - "-Wno-unused-parameter", - "-DUSE_BINDER=1", - ], - export_include_dirs: ["client_library/include"], - include_dirs: [ - // TODO(deymo): Remove "external/cros/system_api/dbus" when dbus is not used. - "external/cros/system_api/dbus", - "system", - ], - - aidl: { - local_include_dirs: ["binder_bindings"], - }, - - shared_libs: [ - "libchrome", - "libbrillo", - "libbinder", - "libbrillo-binder", - "libutils", - ], - - srcs: [ - ":libupdate_engine_client_aidl", - "client_library/client.cc", - "client_library/client_binder.cc", - "parcelable_update_engine_status.cc", - "update_status_utils.cc", - ], -} - -filegroup { - name: "libupdate_engine_client_aidl", - srcs: [ - "binder_bindings/android/brillo/IUpdateEngine.aidl", - "binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl", - ], - path: "binder_bindings", -} - // update_engine_client (type: executable) // ======================================================== // update_engine console client. @@ -478,8 +509,8 @@ cc_binary { srcs: [ ":libupdate_engine_aidl", + "aosp/update_engine_client_android.cc", "common/error_code_utils.cc", - "update_engine_client_android.cc", "update_status_utils.cc", ], } @@ -509,6 +540,8 @@ cc_defaults { "libpuffdiff", "libverity_tree", "update_metadata-protos", + "libpayload_extent_utils", + "libcow_size_estimator", ], shared_libs: [ "libbase", @@ -517,6 +550,40 @@ cc_defaults { } cc_library_static { + name: "libpayload_extent_ranges", + defaults: [ + "ue_defaults", + ], + host_supported: true, + recovery_available: true, + srcs: [ + "payload_generator/extent_ranges.cc", + ], + static_libs: [ + "update_metadata-protos", + ], +} + +cc_library_static { + name: "libcow_size_estimator", + defaults: [ + "ue_defaults", + "update_metadata-protos_exports" + ], + host_supported: true, + recovery_available: true, + srcs: [ + "payload_generator/cow_size_estimator.cc", + ], + static_libs: [ + "update_metadata-protos", + "libbase", + "libsnapshot_cow", + "libcow_operation_convert", + ], +} + +cc_library_static { name: "libpayload_generator", defaults: [ "ue_defaults", @@ -525,32 +592,29 @@ cc_library_static { host_supported: true, srcs: [ + "common/system_state.cc", + "download_action.cc", "payload_generator/ab_generator.cc", "payload_generator/annotated_operation.cc", "payload_generator/blob_file_writer.cc", "payload_generator/block_mapping.cc", "payload_generator/boot_img_filesystem.cc", "payload_generator/bzip.cc", - "payload_generator/cycle_breaker.cc", "payload_generator/deflate_utils.cc", "payload_generator/delta_diff_generator.cc", "payload_generator/delta_diff_utils.cc", "payload_generator/ext2_filesystem.cc", "payload_generator/extent_ranges.cc", - "payload_generator/extent_utils.cc", "payload_generator/full_update_generator.cc", - "payload_generator/graph_types.cc", - "payload_generator/graph_utils.cc", - "payload_generator/inplace_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", + "payload_generator/payload_properties.cc", "payload_generator/payload_signer.cc", "payload_generator/raw_filesystem.cc", "payload_generator/squashfs_filesystem.cc", - "payload_generator/tarjan.cc", - "payload_generator/topological_sort.cc", "payload_generator/xz_android.cc", ], } @@ -701,16 +765,22 @@ cc_test { test_suites: ["device-tests"], srcs: [ + "aosp/apex_handler_android_unittest.cc", + "aosp/cleanup_previous_update_action_unittest.cc", + "aosp/dynamic_partition_control_android_unittest.cc", + "aosp/update_attempter_android_unittest.cc", "certificate_checker_unittest.cc", "common/action_pipe_unittest.cc", "common/action_processor_unittest.cc", "common/action_unittest.cc", + "common/cow_operation_convert_unittest.cc", "common/cpu_limiter_unittest.cc", "common/fake_prefs.cc", "common/file_fetcher_unittest.cc", "common/hash_calculator_unittest.cc", "common/http_fetcher_unittest.cc", "common/hwid_override_unittest.cc", + "common/metrics_reporter_stub.cc", "common/mock_http_fetcher.cc", "common/prefs_unittest.cc", "common/proxy_resolver_unittest.cc", @@ -718,18 +788,24 @@ cc_test { "common/terminator_unittest.cc", "common/test_utils.cc", "common/utils_unittest.cc", - "dynamic_partition_control_android_unittest.cc", + "download_action_android_unittest.cc", + "libcurl_http_fetcher_unittest.cc", "payload_consumer/bzip_extent_writer_unittest.cc", "payload_consumer/cached_file_descriptor_unittest.cc", + "payload_consumer/cow_writer_file_descriptor_unittest.cc", "payload_consumer/certificate_parser_android_unittest.cc", "payload_consumer/delta_performer_integration_test.cc", "payload_consumer/delta_performer_unittest.cc", + "payload_consumer/partition_writer_unittest.cc", "payload_consumer/extent_reader_unittest.cc", "payload_consumer/extent_writer_unittest.cc", + "payload_consumer/snapshot_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/install_plan_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", @@ -737,7 +813,6 @@ cc_test { "payload_generator/blob_file_writer_unittest.cc", "payload_generator/block_mapping_unittest.cc", "payload_generator/boot_img_filesystem_unittest.cc", - "payload_generator/cycle_breaker_unittest.cc", "payload_generator/deflate_utils_unittest.cc", "payload_generator/delta_diff_utils_unittest.cc", "payload_generator/ext2_filesystem_unittest.cc", @@ -745,30 +820,28 @@ cc_test { "payload_generator/extent_utils_unittest.cc", "payload_generator/fake_filesystem.cc", "payload_generator/full_update_generator_unittest.cc", - "payload_generator/graph_utils_unittest.cc", - "payload_generator/inplace_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", + "payload_generator/payload_properties_unittest.cc", "payload_generator/payload_signer_unittest.cc", "payload_generator/squashfs_filesystem_unittest.cc", - "payload_generator/tarjan_unittest.cc", - "payload_generator/topological_sort_unittest.cc", "payload_generator/zip_unittest.cc", "testrunner.cc", - "update_attempter_android_unittest.cc", + "update_status_utils_unittest.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", @@ -782,11 +855,49 @@ cc_prebuilt_binary { }, } -// AIDL interface between libupdate_engine and the Things jar. -filegroup { - name: "things_update_engine_aidl", +// update_engine header library +cc_library_headers { + name: "libupdate_engine_headers", + + // This header library is available to core and product modules. + product_available: true, + + export_include_dirs: ["."], + apex_available: [ + "com.android.gki.*", + "//apex_available:platform", + ], + host_supported: true, + recovery_available: true, + ramdisk_available: true, + + target: { + darwin: { + enabled: false, + }, + } +} + +cc_binary_host { + name: "cow_converter", + defaults: [ + "ue_defaults", + "libpayload_consumer_exports", + ], srcs: [ - "binder_bindings/android/brillo/IUpdateEngine.aidl", - "binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl", + "aosp/cow_converter.cc", + ], + static_libs: [ + "liblog", + "libbrotli", + "libbase", + "libcow_operation_convert", + "libcow_size_estimator", + "libpayload_consumer", + "libpayload_extent_ranges", + "libpayload_extent_utils", + "libsnapshot_cow", + "libz", + "update_metadata-protos", ], } diff --git a/BUILD.gn b/BUILD.gn new file mode 100644 index 00000000..e60d33bd --- /dev/null +++ b/BUILD.gn @@ -0,0 +1,609 @@ +# +# 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. +# + +# Stop linter from complaining XXX_unittest.cc naming. +# gnlint: disable=GnLintSourceFileNames + +import("//common-mk/generate-dbus-adaptors.gni") +import("//common-mk/generate-dbus-proxies.gni") +import("//common-mk/openssl_pem.gni") +import("//common-mk/pkg_config.gni") +import("//common-mk/proto_library.gni") +import("//update_engine/tar_bunzip2.gni") + +group("all") { + deps = [ + ":delta_generator", + ":libpayload_consumer", + ":libpayload_generator", + ":libupdate_engine", + ":libupdate_engine_client", + ":update_engine", + ":update_engine-dbus-adaptor", + ":update_engine-dbus-kiosk-app-client", + ":update_engine_client", + ":update_metadata-protos", + ] + + if (use.test) { + deps += [ + ":test_http_server", + ":test_subprocess", + ":update_engine-test_images", + ":update_engine-testkeys", + ":update_engine-testkeys-ec", + ":update_engine_test_libs", + ":update_engine_unittests", + ] + } + + if (use.fuzzer) { + deps += [ + ":update_engine_delta_performer_fuzzer", + ":update_engine_omaha_request_action_fuzzer", + ] + } +} + +pkg_config("target_defaults") { + cflags_cc = [ + "-fno-strict-aliasing", + "-Wnon-virtual-dtor", + ] + cflags = [ "-ffunction-sections" ] + ldflags = [ "-Wl,--gc-sections" ] + defines = [ + "__CHROMEOS__", + "_FILE_OFFSET_BITS=64", + "_POSIX_C_SOURCE=199309L", + "USE_CFM=${use.cfm}", + "USE_DBUS=${use.dbus}", + "USE_FEC=0", + "USE_HWID_OVERRIDE=${use.hwid_override}", + ] + include_dirs = [ + # We need this include dir because we include all the local code as + # "update_engine/...". + "${platform2_root}", + "${platform2_root}/update_engine/client_library/include", + ] + + # NOSORT + pkg_deps = [ + "libbrillo", + "libchrome", + + # system_api depends on protobuf (or protobuf-lite). It must appear + # before protobuf here or the linker flags won't be in the right + # order. + "system_api", + ] + if (use.fuzzer) { + pkg_deps += [ "protobuf" ] + } else { + pkg_deps += [ "protobuf-lite" ] + } +} + +# Protobufs. +proto_library("update_metadata-protos") { + proto_in_dir = "." + proto_out_dir = "include/update_engine" + sources = [ "update_metadata.proto" ] +} + +# Chrome D-Bus bindings. +generate_dbus_adaptors("update_engine-dbus-adaptor") { + dbus_adaptors_out_dir = "include/dbus_bindings" + sources = [ "dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml" ] +} + +generate_dbus_proxies("update_engine-dbus-kiosk-app-client") { + mock_output_file = "include/kiosk-app/dbus-proxy-mocks.h" + proxy_output_file = "include/kiosk-app/dbus-proxies.h" + sources = [ "dbus_bindings/org.chromium.KioskAppService.dbus-xml" ] +} + +# The payload application component and common dependencies. +static_library("libpayload_consumer") { + sources = [ + "common/action_processor.cc", + "common/boot_control_stub.cc", + "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", + "common/http_fetcher.cc", + "common/hwid_override.cc", + "common/multi_range_http_fetcher.cc", + "common/prefs.cc", + "common/proxy_resolver.cc", + "common/subprocess.cc", + "common/terminator.cc", + "common/utils.cc", + "cros/platform_constants_chromeos.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/extent_reader.cc", + "payload_consumer/extent_writer.cc", + "payload_consumer/file_descriptor.cc", + "payload_consumer/file_descriptor_utils.cc", + "payload_consumer/file_writer.cc", + "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/partition_writer_factory_chromeos.cc", + "payload_consumer/partition_writer.cc", + "payload_consumer/payload_constants.cc", + "payload_consumer/payload_metadata.cc", + "payload_consumer/payload_verifier.cc", + "payload_consumer/postinstall_runner_action.cc", + "payload_consumer/verity_writer_stub.cc", + "payload_consumer/xz_extent_writer.cc", + ] + configs += [ ":target_defaults" ] + libs = [ + "bz2", + "rt", + ] + + # TODO(crbug.com/1082873): Remove after fixing usage of deprecated + # declarations. + cflags_cc = [ "-Wno-error=deprecated-declarations" ] + + # TODO(deymo): Remove unused dependencies once we stop including files + # from the root directory. + all_dependent_pkg_deps = [ + "libbspatch", + "libcrypto", + "libpuffpatch", + "xz-embedded", + ] + public_deps = [ ":update_metadata-protos" ] +} + +# The main daemon static_library with all the code used to check for updates +# with Omaha and expose a DBus daemon. +static_library("libupdate_engine") { + sources = [ + "certificate_checker.cc", + "common/connection_utils.cc", + "common/system_state.cc", + "cros/boot_control_chromeos.cc", + "cros/common_service.cc", + "cros/connection_manager.cc", + "cros/daemon_chromeos.cc", + "cros/dbus_connection.cc", + "cros/dbus_service.cc", + "cros/hardware_chromeos.cc", + "cros/image_properties_chromeos.cc", + "cros/logging.cc", + "cros/metrics_reporter_omaha.cc", + "cros/omaha_request_action.cc", + "cros/omaha_request_builder_xml.cc", + "cros/omaha_request_params.cc", + "cros/omaha_response_handler_action.cc", + "cros/omaha_utils.cc", + "cros/p2p_manager.cc", + "cros/payload_state.cc", + "cros/power_manager_chromeos.cc", + "cros/real_system_state.cc", + "cros/requisition_util.cc", + "cros/shill_proxy.cc", + "cros/update_attempter.cc", + "cros/download_action_chromeos.cc", + "libcurl_http_fetcher.cc", + "metrics_utils.cc", + "update_boot_flags_action.cc", + "update_manager/boxed_value.cc", + "update_manager/chromeos_policy.cc", + "update_manager/default_policy.cc", + "update_manager/enough_slots_ab_updates_policy_impl.cc", + "update_manager/enterprise_device_policy_impl.cc", + "update_manager/enterprise_rollback_policy_impl.cc", + "update_manager/evaluation_context.cc", + "update_manager/interactive_update_policy_impl.cc", + "update_manager/minimum_version_policy_impl.cc", + "update_manager/next_update_check_policy_impl.cc", + "update_manager/official_build_check_policy_impl.cc", + "update_manager/out_of_box_experience_policy_impl.cc", + "update_manager/policy.cc", + "update_manager/policy_test_utils.cc", + "update_manager/real_config_provider.cc", + "update_manager/real_device_policy_provider.cc", + "update_manager/real_random_provider.cc", + "update_manager/real_shill_provider.cc", + "update_manager/real_system_provider.cc", + "update_manager/real_time_provider.cc", + "update_manager/real_updater_provider.cc", + "update_manager/staging_utils.cc", + "update_manager/state_factory.cc", + "update_manager/update_manager.cc", + "update_manager/update_time_restrictions_monitor.cc", + "update_manager/update_time_restrictions_policy_impl.cc", + "update_manager/weekly_time.cc", + "update_status_utils.cc", + ] + configs += [ ":target_defaults" ] + libs = [ + "bz2", + "policy", + "rootdev", + "rt", + ] + all_dependent_pkg_deps = [ + "dbus-1", + "expat", + "libcurl", + "libdebugd-client", + "libmetrics", + "libpower_manager-client", + "libsession_manager-client", + "libshill-client", + "libssl", + "libupdate_engine-client", + "vboot_host", + ] + deps = [ + ":libpayload_consumer", + ":update_engine-dbus-adaptor", + ":update_engine-dbus-kiosk-app-client", + ":update_metadata-protos", + ] + + if (use.dlc) { + all_dependent_pkg_deps += [ "libdlcservice-client" ] + } + + if (use.chrome_network_proxy) { + sources += [ "cros/chrome_browser_proxy_resolver.cc" ] + } + + if (use.dlc) { + sources += [ + "cros/dlcservice_chromeos.cc", + "cros/excluder_chromeos.cc", + ] + } else { + sources += [ + "common/dlcservice_stub.cc", + "common/excluder_stub.cc", + ] + } +} + +# update_engine daemon. +executable("update_engine") { + sources = [ "main.cc" ] + configs += [ ":target_defaults" ] + deps = [ ":libupdate_engine" ] +} + +# update_engine client library. +static_library("libupdate_engine_client") { + sources = [ + "client_library/client_dbus.cc", + "update_status_utils.cc", + ] + include_dirs = [ "client_library/include" ] + configs += [ ":target_defaults" ] + pkg_deps = [ + "dbus-1", + "libupdate_engine-client", + ] +} + +# update_engine console client. +executable("update_engine_client") { + sources = [ + "common/error_code_utils.cc", + "cros/omaha_utils.cc", + "cros/update_engine_client.cc", + ] + configs += [ ":target_defaults" ] + deps = [ ":libupdate_engine_client" ] +} + +# server-side code. This is used for delta_generator and unittests but not +# for any client code. +static_library("libpayload_generator") { + sources = [ + "common/file_fetcher.cc", + "common/system_state.cc", + "cros/real_system_state.cc", + "download_action.cc", + "payload_generator/ab_generator.cc", + "payload_generator/annotated_operation.cc", + "payload_generator/blob_file_writer.cc", + "payload_generator/block_mapping.cc", + "payload_generator/boot_img_filesystem_stub.cc", + "payload_generator/bzip.cc", + "payload_generator/cow_size_estimator_stub.cc", + "payload_generator/deflate_utils.cc", + "payload_generator/delta_diff_generator.cc", + "payload_generator/delta_diff_utils.cc", + "payload_generator/ext2_filesystem.cc", + "payload_generator/extent_ranges.cc", + "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", + "payload_generator/payload_properties.cc", + "payload_generator/payload_signer.cc", + "payload_generator/raw_filesystem.cc", + "payload_generator/squashfs_filesystem.cc", + "payload_generator/xz_chromeos.cc", + ] + configs += [ ":target_defaults" ] + all_dependent_pkg_deps = [ + "ext2fs", + "libbsdiff", + "liblzma", + "libpuffdiff", + ] + deps = [ + ":libpayload_consumer", + ":update_metadata-protos", + ] + + # TODO(crbug.com/1082873): Remove after fixing usage of deprecated + # declarations. + cflags_cc = [ "-Wno-error=deprecated-declarations" ] +} + +# server-side delta generator. +executable("delta_generator") { + sources = [ "payload_generator/generate_delta_main.cc" ] + configs += [ ":target_defaults" ] + configs -= [ "//common-mk:pie" ] + deps = [ + ":libpayload_consumer", + ":libpayload_generator", + ] +} + +if (use.test || use.fuzzer) { + static_library("update_engine_test_libs") { + sources = [ + "common/fake_prefs.cc", + "common/mock_http_fetcher.cc", + "common/test_utils.cc", + "cros/fake_shill_proxy.cc", + "cros/fake_system_state.cc", + "payload_consumer/fake_file_descriptor.cc", + "payload_generator/fake_filesystem.cc", + "update_manager/umtest_utils.cc", + ] + + # TODO(crbug.com/887845): After library odering issue is fixed, + # //common-mk:test can be moved in all_dependent_configs and + # //common-mk:test in each test configs can be removed. + configs += [ + "//common-mk:test", + ":target_defaults", + ] + pkg_deps = [ "libshill-client-test" ] + deps = [ ":libupdate_engine" ] + } +} + +if (use.test) { + # Public keys used for unit testing. + genopenssl_key("update_engine-testkeys") { + openssl_pem_in_dir = "." + openssl_pem_out_dir = "include/update_engine" + sources = [ + "unittest_key.pem", + "unittest_key2.pem", + "unittest_key_RSA4096.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 = "." + sources = [ "sample_images/sample_images.tar.bz2" ] + } + + # Test HTTP Server. + executable("test_http_server") { + sources = [ + "common/http_common.cc", + "test_http_server.cc", + ] + + # //common-mk:test should be on the top. + # TODO(crbug.com/887845): Remove this after library odering issue is fixed. + configs += [ + "//common-mk:test", + ":target_defaults", + ] + } + + # Test subprocess helper. + executable("test_subprocess") { + sources = [ "test_subprocess.cc" ] + + # //common-mk:test should be on the top. + # TODO(crbug.com/887845): Remove this after library odering issue is fixed. + configs += [ + "//common-mk:test", + ":target_defaults", + ] + } + + # Main unittest file. + executable("update_engine_unittests") { + sources = [ + "certificate_checker_unittest.cc", + "common/action_pipe_unittest.cc", + "common/action_processor_unittest.cc", + "common/action_unittest.cc", + "common/cpu_limiter_unittest.cc", + "common/hash_calculator_unittest.cc", + "common/http_fetcher_unittest.cc", + "common/hwid_override_unittest.cc", + "common/prefs_unittest.cc", + "common/proxy_resolver_unittest.cc", + "common/subprocess_unittest.cc", + "common/terminator_unittest.cc", + "common/utils_unittest.cc", + "cros/boot_control_chromeos_unittest.cc", + "cros/common_service_unittest.cc", + "cros/connection_manager_unittest.cc", + "cros/hardware_chromeos_unittest.cc", + "cros/image_properties_chromeos_unittest.cc", + "cros/metrics_reporter_omaha_unittest.cc", + "cros/omaha_request_action_unittest.cc", + "cros/omaha_request_builder_xml_unittest.cc", + "cros/omaha_request_params_unittest.cc", + "cros/omaha_response_handler_action_unittest.cc", + "cros/omaha_utils_unittest.cc", + "cros/p2p_manager_unittest.cc", + "cros/payload_state_unittest.cc", + "cros/requisition_util_unittest.cc", + "cros/update_attempter_unittest.cc", + "cros/download_action_chromeos_unittest.cc", + "libcurl_http_fetcher_unittest.cc", + "metrics_utils_unittest.cc", + "payload_consumer/bzip_extent_writer_unittest.cc", + "payload_consumer/cached_file_descriptor_unittest.cc", + "payload_consumer/delta_performer_integration_test.cc", + "payload_consumer/delta_performer_unittest.cc", + "payload_consumer/extent_reader_unittest.cc", + "payload_consumer/extent_writer_unittest.cc", + "payload_consumer/file_descriptor_utils_unittest.cc", + "payload_consumer/file_writer_unittest.cc", + "payload_consumer/filesystem_verifier_action_unittest.cc", + "payload_consumer/install_plan_unittest.cc", + "payload_consumer/postinstall_runner_action_unittest.cc", + "payload_consumer/xz_extent_writer_unittest.cc", + "payload_generator/ab_generator_unittest.cc", + "payload_generator/blob_file_writer_unittest.cc", + "payload_generator/block_mapping_unittest.cc", + "payload_generator/deflate_utils_unittest.cc", + "payload_generator/delta_diff_utils_unittest.cc", + "payload_generator/ext2_filesystem_unittest.cc", + "payload_generator/extent_ranges_unittest.cc", + "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", + "payload_generator/payload_signer_unittest.cc", + "payload_generator/squashfs_filesystem_unittest.cc", + "payload_generator/zip_unittest.cc", + "testrunner.cc", + "update_boot_flags_action_unittest.cc", + "update_manager/boxed_value_unittest.cc", + "update_manager/chromeos_policy_unittest.cc", + "update_manager/enterprise_device_policy_impl_unittest.cc", + "update_manager/enterprise_rollback_policy_impl_unittest.cc", + "update_manager/evaluation_context_unittest.cc", + "update_manager/generic_variables_unittest.cc", + "update_manager/minimum_version_policy_impl_unittest.cc", + "update_manager/prng_unittest.cc", + "update_manager/real_device_policy_provider_unittest.cc", + "update_manager/real_random_provider_unittest.cc", + "update_manager/real_shill_provider_unittest.cc", + "update_manager/real_system_provider_unittest.cc", + "update_manager/real_time_provider_unittest.cc", + "update_manager/real_updater_provider_unittest.cc", + "update_manager/staging_utils_unittest.cc", + "update_manager/update_manager_unittest.cc", + "update_manager/update_time_restrictions_monitor_unittest.cc", + "update_manager/update_time_restrictions_policy_impl_unittest.cc", + "update_manager/variable_unittest.cc", + "update_manager/weekly_time_unittest.cc", + "update_status_utils_unittest.cc", + ] + if (use.dlc) { + sources += [ "cros/excluder_chromeos_unittest.cc" ] + } + + # //common-mk:test should be on the top. + # TODO(crbug.com/887845): Remove this after library odering issue is fixed. + configs += [ + "//common-mk:test", + ":target_defaults", + ] + pkg_deps = [ + "libbrillo-test", + "libchrome-test", + "libdebugd-client-test", + "libpower_manager-client-test", + "libsession_manager-client-test", + "libshill-client-test", + ] + deps = [ + ":libpayload_generator", + ":libupdate_engine", + ":update_engine_test_libs", + ] + } +} + +# Fuzzer target. +if (use.fuzzer) { + executable("update_engine_delta_performer_fuzzer") { + sources = [ "payload_consumer/delta_performer_fuzzer.cc" ] + configs += [ + "//common-mk/common_fuzzer", + ":target_defaults", + ] + pkg_deps = [ + "libbrillo-test", + "libchrome-test", + ] + deps = [ + ":libupdate_engine", + ":update_engine_test_libs", + ] + } + executable("update_engine_omaha_request_action_fuzzer") { + sources = [ "cros/omaha_request_action_fuzzer.cc" ] + configs += [ + "//common-mk/common_fuzzer", + ":target_defaults", + ] + pkg_deps = [ + "libbrillo-test", + "libchrome-test", + ] + deps = [ + ":libupdate_engine", + ":update_engine_test_libs", + ] + } +} diff --git a/COMMIT-QUEUE.ini b/COMMIT-QUEUE.ini deleted file mode 100644 index ed99b9ff..00000000 --- a/COMMIT-QUEUE.ini +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2017 The Chromium OS Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -# Per-project Commit Queue settings. -# Documentation: http://goo.gl/5J7oND - -[GENERAL] - -# Moblab testing is needed because of the udpate_payloads ebuild. -pre-cq-configs: default guado_moblab-no-vmtest-pre-cq 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 +} @@ -1,10 +1,14 @@ set noparent -# Current general maintainers: +# Android et. al. maintainers: deymo@google.com +elsk@google.com senj@google.com +xunchang@google.com # Chromium OS maintainers: -benchan@google.com ahassani@google.com -xiaochu@google.com +kimjae@google.com + +# Chromium OS only: +# COMPONENT: Internals>Installer diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg index f2c78315..42156b3e 100644 --- a/PRESUBMIT.cfg +++ b/PRESUBMIT.cfg @@ -1,6 +1,6 @@ [Hook Scripts] -hook0=../../../../chromite/bin/cros lint ${PRESUBMIT_FILES} -hook1=../../../platform2/common-mk/gyplint.py ${PRESUBMIT_FILES} +cros lint = cros lint ${PRESUBMIT_FILES} +gnlint = ../../../platform2/common-mk/gnlint.py ${PRESUBMIT_FILES} [Hook Overrides] clang_format_check: true diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 40ddcd14..85fd5ece 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -2,3 +2,6 @@ clang_format = true cpplint = true pylint = true + +[Hook Scripts] +protobuflint = ./protobuflint.py ${PREUPLOAD_COMMIT} ${PREUPLOAD_FILES} diff --git a/README.md b/README.md new file mode 100644 index 00000000..71f271b2 --- /dev/null +++ b/README.md @@ -0,0 +1,642 @@ +# Chrome OS Update Process + +[TOC] + +System updates in more modern operating systems like Chrome OS and Android are +called A/B updates, over-the-air ([OTA]) updates, seamless updates, or simply +auto updates. In contrast to more primitive system updates (like Windows or +macOS) where the system is booted into a special mode to override the system +partitions with newer updates and may take several minutes or hours, A/B updates +have several advantages including but not limited to: + +* Updates maintain a workable system that remains on the disk during and after + an update. Hence, reducing the likelihood of corrupting a device into a + non-usable state. And reducing the need for flashing devices manually or at + repair and warranty centers, etc. +* Updates can happen while the system is running (normally with minimum + overhead) without interrupting the user. The only downside for users is a + required reboot (or, in Chrome OS, a sign out which automatically causes a + reboot if an update was performed where the reboot duration is about 10 + seconds and is no different than a normal reboot). +* The user does not need (although they can) to request for an update. The + update checks happen periodically in the background. +* If the update fails to apply, the user is not affected. The user will + continue on the old version of the system and the system will attempt to + apply the update again at a later time. +* If the update applies correctly but fails to boot, the system will rollback + to the old partition and the user can still use the system as usual. +* The user does not need to reserve enough space for the update. The system + has already reserved enough space in terms of two copies (A and B) of a + partition. The system doesn’t even need any cache space on the disk, + everything happens seamlessly from network to memory to the inactive + partitions. + +## Life of an A/B Update + +In A/B update capable systems, each partition, such as the kernel or root (or +other artifacts like [DLC]), has two copies. We call these two copies active (A) +and inactive (B). The system is booted into the active partition (depending on +which copy has the higher priority at boot time) and when a new update is +available, it is written into the inactive partition. After a successful reboot, +the previously inactive partition becomes active and the old active partition +becomes inactive. + +But everything starts with generating update payloads in (Google) servers for +each new system image. Once the update payloads are generated, they are signed +with specific keys and stored in a location known to an update server (Omaha). + +When the updater client initiates an update (either periodically or user +initiated), it first consults different device policies to see if the update +check is allowed. For example, device policies can prevent an update check +during certain times of a day or they require the update check time to be +scattered throughout the day randomly, etc. + +Once policies allow for the update check, the updater client sends a request to +the update server (all this communication happens over HTTPS) and identifies its +parameters like its Application ID, hardware ID, version, board, etc. Then if +the update server decides to serve an update payload, it will respond with all +the parameters needed to perform an update like the URLs to download the +payloads, the metadata signatures, the payload size and hash, etc. The updater +client continues communicating with the update server after different state +changes, like reporting that it started to download the payload or it finished +the update, or reports that the update failed with specific error codes, etc. + +Each payload consists of two main sections: metadata and extra data. The +metadata is basically a list of operations that should be performed for an +update. The extra data contains the data blobs needed by some or all of these +operations. The updater client first downloads the metadata and +cryptographically verifies it using the provided signatures from the update +server’s response. Once the metadata is verified as valid, the rest of the +payload can easily be verified cryptographically (mostly through SHA256 hashes). + +Next, the updater client marks the inactive partition as unbootable (because it +needs to write the new updates into it). At this point the system cannot +rollback to the inactive partition anymore. + +Then, the updater client performs the operations defined in the metadata (in the +order they appear in the metadata) and the rest of the payload is gradually +downloaded when these operations require their data. Once an operation is +finished its data is discarded. This eliminates the need for caching the entire +payload before applying it. During this process the updater client periodically +checkpoints the last operation performed so in the event of failure or system +shutdown, etc. it can continue from the point it missed without redoing all +operations from the beginning. + +During the download, the updater client hashes the downloaded bytes and when the +download finishes, it checks the payload signature (located at the end of the +payload). If the signature cannot be verified, the update is rejected. + +After the inactive partition is updated, the entire partition is re-read, hashed +and compared to a hash value passed in the metadata to make sure the update was +successfully written into the partition. + +In the next step, the [Postinstall] process (if any) is called. The postinstall +reconstructs the dm-verity tree hash of the ROOT partition and writes it at the +end of the partition (after the last block of the file system). The postinstall +can also perform any board specific or firmware update tasks necessary. If +postinstall fails, the entire update is considered failed. + +Then the updater client goes into a state that identifies the update has +completed and the user needs to reboot the system. At this point, until the user +reboots (or signs out), the updater client will not do any more system updates +even if newer updates are available. However, it does continue to perform +periodic update checks so we can have statistics on the number of active devices +in the field. + +After the update proved successful, the inactive partition is marked to have a +higher priority (on a boot, a partition with higher priority is booted +first). Once the user reboots the system, it will boot into the updated +partition and it is marked as active. At this point, after the reboot, The +updater client calls into the [`chromeos-setgoodkernel`] program. The program +verifies the integrity of the system partitions using the dm-verity and marks +the active partition as healthy. At this point the system is basically updated +successfully. + +## Update Engine Daemon + +The `update_engine` is a single-threaded daemon process that runs all the +times. This process is the heart of the auto updates. It runs with lower +priorities in the background and is one of the last processes to start after a +system boot. Different clients (like Chrome or other services) can send requests +for update checks to the update engine. The details of how requests are passed +to the update engine is system dependent, but in Chrome OS it is D-Bus. Look at +the [D-Bus interface] for a list of all available methods. + +There are many resiliency features embedded in the update engine that makes auto +updates robust including but not limited to: + +* If the update engine crashes, it will restart automatically. +* During an active update it periodically checkpoints the state of the update + and if it fails to continue the update or crashes in the middle, it will + continue from the last checkpoint. +* It retries failed network communication. +* If it fails to apply a delta payload (due to bit changes on the active + partition) for a few times, it switches to full payload. + +The updater clients writes its active preferences in +`/var/lib/update_engine/prefs`. These preferences help with tracking changes +during the lifetime of the updater client and allows properly continuing the +update process after failed attempts or crashes. + +The core update engine code base in a Chromium OS checkout is located in +`src/aosp/system/update_engine` fetching [this repository]. + +### Policy Management + +In Chrome OS, devices are allowed to accept different policies from their +managing organizations. Some of these policies affect how/when updates should be +performed. For example, an organization may want to scatter the update checks +during certain times of the day so as not to interfere with normal +business. Within the update engine daemon, [UpdateManager] has the +responsibility of loading such policies and making different decisions based on +them. For example, some policies may allow the act of checking for updates to +happen, while they prevent downloading the update payload. Or some policies +don’t allow the update check within certain time frames, etc. Anything that +relates to the Chrome OS update policies should be contained within the +[update_manager] directory in the source code. + +### Rollback vs. Enterprise Rollback + +Chrome OS defines a concept for Rollback: Whenever a newly updated system does +not work as it is intended, under certain circumstances the device can be rolled +back to a previously working version. There are two types of rollback supported +in Chrome OS: A (legacy, original) rollback and an enterprise rollback (I know, +naming is confusing). + +A normal rollback, which has existed for as long as Chrome OS had auto updater, +is performed by switching the currently inactive partition into the active +partition and rebooting into it. It is as simple as running a successful +postinstall on the inactive partition, and rebooting the device. It is a feature +used by Chrome that happens under certain circumstances. Of course rollback +can’t happen if the inactive partition has been tampered with or has been nuked +by the updater client to install an even newer update. Normally a rollback is +followed by a Powerwash which clobbers the stateful partition. + +Enterprise rollback is a new feature added to allow enterprise users to +downgrade the installed image to an older version. It is very similar to a +normal system update, except that an older update payload is downloaded and +installed. There is no direct API for entering into the enterprise rollback. It +is managed by the enterprise device policies only. + +Developers should be careful when touching any rollback related feature and make +sure they know exactly which of these two features they are trying to adapt. + +### Interactive vs Non-Interactive vs. Forced Updates + +Non-interactive updates are updates that are scheduled periodically by the +update engine and happen in the background. Interactive updates, on the other +hand, happen when a user specifically requests an update check (e.g. by clicking +on “Check For Update” button in Chrome OS’s About page). Depending on the update +server's policies, interactive updates have higher priority than non-interactive +updates (by carrying marker hints). They may decide to not provide an update if +they have busy server load, etc. There are other internal differences between +these two types of updates too. For example, interactive updates try to install +the update faster. + +Forced updates are similar to interactive updates (initiated by some kind of +user action), but they can also be configured to act as non-interactive. Since +non-interactive updates happen periodically, a forced-non-interactive update +causes a non-interactive update at the moment of the request, not at a later +time. We can call a forced non-interactive update with: + +```bash +update_engine_client --interactive=false --check_for_update +``` + +### P2P Updates + +Many organizations might not have the external bandwidth requirements that +system updates need for all their devices. To help with this, Chrome OS can act +as a payload server to other client devices in the same network subnet. This is +basically a peer-to-peer update system that allows the devices to download the +update payloads from other devices in the network. This has to be enabled +explicitly in the organization through device policies and specific network +configurations to be enabled for P2P updates to work. Regardless of the location +of update payloads, all update requests go through update servers in HTTPS. + +Check out the [P2P update related code] for both the server and the client side. + +### Network + +The updater client has the capability to download the payloads using Ethernet, +WiFi, or Cellular networks depending on which one the device is connected +to. Downloading over Cellular networks will prompt permission from the user as +it can consume a considerable amount of data. + +### Logs + +In Chrome OS the `update_engine` logs are located in `/var/log/update_engine` +directory. Whenever `update_engine` starts, it starts a new log file with the +current data-time format in the log file’s name +(`update_engine.log-DATE-TIME`). Many log files can be seen in +`/var/log/update_engine` after a few restarts of the update engine or after the +system reboots. The latest active log is symlinked to +`/var/log/update_engine.log`. + +## Update Payload Generation + +The update payload generation is the process of converting a set of +partitions/files into a format that is both understandable by the updater client +(especially if it's a much older version) and is securely verifiable. This +process involves breaking the input partitions into smaller components and +compressing them in order to help with network bandwidth when downloading the +payloads. + +For each generated payload, there is a corresponding properties file which +contains the metadata information of the payload in JSON format. Normally the +file is located in the same location as the generated payload and its file name +is the same as the payload file name plus `.json` +postfix. e.g. `/path/to/payload.bin` and `/path/to/payload.bin.json`. This +properties file is necessary in order to do any kind of auto update in [`cros +flash`], AU autotests, etc. Similarly the updater server uses this file to +dispatch the payload properties to the updater clients. + +Once update payloads are generated, their original images cannot be changed +anymore otherwise the update payloads may not be able to be applied. + +`delta_generator` is a tool with a wide range of options for generating +different types of update payloads. Its code is located in +`update_engine/payload_generator`. This directory contains all the source code +related to mechanics of generating an update payload. None of the files in this +directory should be included or used in any other library/executable other than +the `delta_generator` which means this directory does not get compiled into the +rest of the update engine tools. + +However, it is not recommended to use `delta_generator` directly. To manually +generate payloads easier, [`cros_generate_update_payloads`] should be used. Most +of the higher level policies and tools for generating payloads reside as a +library in [`chromite/lib/paygen`]. Whenever calls to the update payload +generation API are needed, this library should be used instead. + +### Update Payload File Specification + +Each update payload file has a specific structure defined in the table below: + +|Field|Size (bytes)|Type|Description| +|-----|------------|----|-----------| +|Magic Number|4|char[4]|Magic string "CrAU" identifying this is an update payload.| +|Major Version|8|uint64|Payload major version number.| +|Manifest Size|8|uint64|Manifest size in bytes.| +|Manifest Signature Size|4|uint32|Manifest signature blob size in bytes (only in major version 2).| +|Manifest|Varies|[DeltaArchiveManifest]|The list of operations to be performed.| +|Manifest Signature|Varies|[Signatures]|The signature of the first five fields. There could be multiple signatures if the key has changed.| +|Payload Data|Varies|List of raw or compressed data blobs|The list of binary blobs used by operations in the metadata.| +|Payload Signature Size|Varies|uint64|The size of the payload signature.| +|Payload Signature|Varies|[Signatures]|The signature of the entire payload except the metadata signature. There could be multiple signatures if the key has changed.| + +### Delta vs. Full Update Payloads + +There are two types of payload: Full and Delta. A full payload is generated +solely from the target image (the image we want to update to) and has all the +data necessary to update the inactive partition. Hence, full payloads can be +quite large in size. A delta payload, on the other hand, is a differential +update generated by comparing the source image (the active partitions) and the +target image and producing the diffs between these two images. It is basically a +differential update similar to applications like `diff` or `bsdiff`. Hence, +updating the system using the delta payloads requires the system to read parts +of the active partition in order to update the inactive partition (or +reconstruct the target partition). The delta payloads are significantly smaller +than the full payloads. The structure of the payload is equal for both types. + +Payload generation is quite resource intensive and its tools are implemented +with high parallelism. + +#### Generating Full Payloads + +A full payload is generated by breaking the partition into 2MiB (configurable) +chunks and either compressing them using bzip2 or XZ algorithms or keeping it as +raw data depending on which produces smaller data. Full payloads are much larger +in comparison to delta payloads hence require longer download time if the +network bandwidth is limited. On the other hand, full payloads are a bit faster +to apply because the system doesn’t need to read data from the source partition. + +#### Generating Delta Payloads + +Delta payloads are generated by looking at both the source and target images +data on a file and metadata basis (more precisely, the file system level on each +appropriate partition). The reason we can generate delta payloads is that Chrome +OS partitions are read only. So with high certainty we can assume the active +partitions on the client’s device is bit-by-bit equal to the original partitions +generated in the image generation/signing phase. The process for generating a +delta payload is roughly as follows: + +1. Find all the zero-filled blocks on the target partition and produce `ZERO` + operation for them. `ZERO` operation basically discards the associated + blocks (depending on the implementation). +2. Find all the blocks that have not changed between the source and target + partitions by directly comparing one-to-one source and target blocks and + produce `SOURCE_COPY` operation. +3. List all the files (and their associated blocks) in the source and target + partitions and remove blocks (and files) which we have already generated + operations for in the last two steps. Assign the remaining metadata (inodes, + etc) of each partition as a file. +4. If a file is new, generate a `REPLACE`, `REPLACE_XZ`, or `REPLACE_BZ` + operation for its data blocks depending on which one generates a smaller + data blob. +5. For each other file, compare the source and target blocks and produce a + `SOURCE_BSDIFF` or `PUFFDIFF` operation depending on which one generates a + smaller data blob. These two operations produce binary diffs between a + source and target data blob. (Look at [bsdiff] and [puffin] for details of + such binary differential programs!) +6. Sort the operations based on their target partitions’ block offset. +7. Optionally merge same or similar operations next to each other into larger + operations for better efficiency and potentially smaller payloads. + +Full payloads can only contain `REPLACE`, `REPLACE_BZ`, and `REPLACE_XZ` +operations. Delta payloads can contain any operations. + +### Major and Minor versions + +The major and minor versions specify the update payload file format and the +capability of the updater client to accept certain types of update payloads +respectively. These numbers are [hard coded] in the updater client. + +Major version is basically the update payload file version specified in the +[update payload file specification] above (second field). Each updater client +supports a range of major versions. Currently, there are only two major +versions: 1, and 2. And both Chrome OS and Android are on major version 2 (major +version 1 is being deprecated). Whenever there are new additions that cannot be +fitted in the [Manifest protobuf], we need to uprev the major version. Upreving +major version should be done with utmost care because older clients do not know +how to handle the newer versions. Any major version uprev in Chrome OS should be +associated with a GoldenEye stepping stone. + +Minor version defines the capability of the updater client to accept certain +operations or perform certain actions. Each updater client supports a range of +minor versions. For example, the updater client with minor version 4 (or less) +does not know how to handle a `PUFFDIFF` operation. So when generating a delta +payload for an image which has an updater client with minor version 4 (or less) +we cannot produce PUFFDIFF operation for it. The payload generation process +looks at the source image’s minor version to decide the type of operations it +supports and only a payload that confirms to those restrictions. Similarly, if +there is a bug in a client with a specific minor version, an uprev in the minor +version helps with avoiding to generate payloads that cause that bug to +manifest. However, upreving minor versions is quite expensive too in terms of +maintainability and it can be error prone. So one should practice caution when +making such a change. + +Minor versions are irrelevant in full payloads. Full payloads should always be +able to be applied for very old clients. The reason is that the updater clients +may not send their current version, so if we had different types of full +payloads, we would not have known which version to serve to the client. + +### Signed vs Unsigned Payloads + +Update payloads can be signed (with private/public key pairs) for use in +production or be kept unsigned for use in testing. Tools like `delta_generator` +help with generating metadata and payload hashes or signing the payloads given +private keys. + +## update_payload Scripts + +[update_payload] contains a set of python scripts used mostly to validate +payload generation and application. We normally test the update payloads using +an actual device (live tests). [`brillo_update_payload`] script can be used to +generate and test applying of a payload on a host device machine. These tests +can be viewed as dynamic tests without the need for an actual device. Other +`update_payload` scripts (like [`check_update_payload`]) can be used to +statically check that a payload is in the correct state and its application +works correctly. These scripts actually apply the payload statically without +running the code in payload_consumer. + +## Postinstall + +[Postinstall] is a process called after the updater client writes the new image +artifacts to the inactive partitions. One of postinstall's main responsibilities +is to recreate the dm-verity tree hash at the end of the root partition. Among +other things, it installs new firmware updates or any board specific +processes. Postinstall runs in separate chroot inside the newly installed +partition. So it is quite separated from the rest of the active running +system. Anything that needs to be done after an update and before the device is +rebooted, should be implemented inside the postinstall. + +## Building Update Engine + +You can build `update_engine` the same as other platform applications: + +```bash +(chroot) $ emerge-${BOARD} update_engine +``` +or to build without the source copy: + +```bash +(chroot) $ cros_workon_make --board=${BOARD} update_engine +``` + +After a change in the `update_engine` daemon, either build an image and install +the image on the device using cros flash, etc. or use `cros deploy` to only +install the `update_engine` service on the device: + +```bash +(chroot) $ cros deploy update_engine +``` + +You need to restart the `update_engine` daemon in order to see the affected +changes: + +```bash +# SSH into the device. +restart update-engine # with a dash not underscore. +``` + +Other payload generation tools like `delta_generator` are board agnostic and +only available in the SDK. So in order to make any changes to the +`delta_generator`, you should build the SDK: + +```bash +# Do it only once to start building the 9999 ebuild from ToT. +(chroot) $ cros_workon --host start update_engine + +(chroot) $ sudo emerge update_engine +``` + +If you make any changes to the D-Bus interface make sure `system_api`, +`update_engine-client`, and `update_engine` packages are marked to build from +9999 ebuild and then build both packages in that order: + +```bash +(chroot) $ emerge-${BOARD} system_api update_engine-client update_engine +``` + +If you make any changes to [`update_engine` protobufs] in the `system_api`, +build the `system_api` package first. + +## Running Unit Tests + +[Running unit tests similar to other platforms]: + +```bash +(chroot) $ FEATURES=test emerge-<board> update_engine +``` + +or + +```bash +(chroot) $ cros_workon_make --board=<board> --test update_engine +``` + +or + +```bash +(chroot) $ cros_run_unit_tests --board ${BOARD} --packages update_engine +``` + +The above commands run all the unit tests, but `update_engine` package is quite +large and it takes a long time to run all the unit tests. To run all unit tests +in a test class run: + +```bash +(chroot) $ FEATURES=test \ + P2_TEST_FILTER="*OmahaRequestActionTest.*-*RunAsRoot*" \ + emerge-amd64-generic update_engine +``` + +To run one exact unit test fixture (e.g. `MultiAppUpdateTest`), run: + +```bash +(chroot) $ FEATURES=test \ + P2_TEST_FILTER="*OmahaRequestActionTest.MultiAppUpdateTest-*RunAsRoot*" \ + emerge-amd64-generic update_engine +``` + +To run `update_payload` unit tests enter `update_engine/scripts` directory and +run the desired `unittest.p`y files. + +## Initiating a Configured Update + +There are different methods to initiate an update: + +* Click on the “Check For Update” button in setting’s About page. There is no + way to configure this way of update check. +* Use the [`update_engine_client`] program. There are a few configurations you + can do. +* Call `autest` in the crosh. Mainly used by the QA team and is not intended + to be used by any other team. +* Use [`cros flash`]. It internally uses the update_engine to flash a device + with a given image. +* Run one of many auto update autotests. +* Start a [Dev Server] on your host machine and send a specific HTTP request + (look at `cros_au` API in the Dev Server code), that has the information + like the IP address of your Chromebook and where the update payloads are + located to the Dev Server to start an update on your device (**Warning:** + complicated to do, not recommended). + +`update_engine_client` is a client application that can help initiate an update +or get more information about the status of the updater client. It has several +options like initiating an interactive vs. non-interactive update, changing +channels, getting the current status of update process, doing a rollback, +changing the Omaha URL to download the payload (the most important one), etc. + +`update_engine` daemon reads the `/etc/lsb-release` file on the device to +identify different update parameters like the updater server (Omaha) URL, the +current channel, etc. However, to override any of these parameters, create the +file `/mnt/stateful_partition/etc/lsb-release` with desired customized +parameters. For example, this can be used to point to a developer version of the +update server and allow the update_engine to schedule a periodic update from +that specific server. + +If you have some changes in the protocol that communicates with Omaha, but you +don’t have those changes in the update server, or you have some specific +payloads that do not exist on the production update server you can use +[Nebraska] to help with doing an update. + +## Note to Developers and Maintainers + +When changing the update engine source code be extra careful about these things: + +### Do NOT Break Backward Compatibility + +At each release cycle we should be able to generate full and delta payloads that +can correctly be applied to older devices that run older versions of the update +engine client. So for example, removing or not passing arguments in the metadata +proto file might break older clients. Or passing operations that are not +understood in older clients will break them. Whenever changing anything in the +payload generation process, ask yourself this question: Would it work on older +clients? If not, do I need to control it with minor versions or any other means. + +Especially regarding enterprise rollback, a newer updater client should be able +to accept an older update payload. Normally this happens using a full payload, +but care should be taken in order to not break this compatibility. + +### Think About The Future + +When creating a change in the update engine, think about 5 years from now: + +* How can the change be implemented that five years from now older clients + don’t break? +* How is it going to be maintained five years from now? +* How can it make it easier for future changes without breaking older clients + or incurring heavy maintenance costs? + +### Prefer Not To Implement Your Feature In The Updater Client +If a feature can be implemented from server side, Do NOT implement it in the +client updater. Because the client updater can be fragile at points and small +mistakes can have catastrophic consequences. For example, if a bug is introduced +in the updater client that causes it to crash right before checking for update +and we can't quite catch this bug early in the release process, then the +production devices which have already moved to the new buggy system, may no +longer receive automatic updates anymore. So, always think if the feature is +being implemented can be done form the server side (with potentially minimal +changes to the client updater)? Or can the feature be moved to another service +with minimal interface to the updater client. Answering these questions will pay +off greatly in the future. + +### Be Respectful Of Other Code Bases + +The current update engine code base is used in many projects like Android. We +sync the code base among these two projects frequently. Try to not break Android +or other systems that share the update engine code. Whenever landing a change, +always think about whether Android needs that change: + +* How will it affect Android? +* Can the change be moved to an interface and stubs implementations be + implemented so as not to affect Android? +* Can Chrome OS or Android specific code be guarded by macros? + +As a basic measure, if adding/removing/renaming code, make sure to change both +`build.gn` and `Android.bp`. Do not bring Chrome OS specific code (for example +other libraries that live in `system_api` or `dlcservice`) into the common code +of update_engine. Try to separate these concerns using best software engineering +practices. + +### Merging from Android (or other code bases) + +Chrome OS tracks the Android code as an [upstream branch]. To merge the Android +code to Chrome OS (or vice versa) just do a `git merge` of that branch into +Chrome OS, test it using whatever means and upload a merge commit. + +```bash +repo start merge-aosp +git merge --no-ff --strategy=recursive -X patience cros/upstream +repo upload --cbr --no-verify . +``` + +[Postinstall]: #postinstall +[update payload file specification]: #update-payload-file-specification +[OTA]: https://source.android.com/devices/tech/ota +[DLC]: https://chromium.googlesource.com/chromiumos/platform2/+/master/dlcservice +[`chromeos-setgoodkernel`]: https://chromium.googlesource.com/chromiumos/platform2/+/master/installer/chromeos-setgoodkernel +[D-Bus interface]: /dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml +[this repository]: / +[UpdateManager]: /update_manager/update_manager.cc +[update_manager]: /update_manager/ +[P2P update related code]: https://chromium.googlesource.com/chromiumos/platform2/+/master/p2p/ +[`cros_generate_update_payloads`]: https://chromium.googlesource.com/chromiumos/chromite/+/master/scripts/cros_generate_update_payload.py +[`chromite/lib/paygen`]: https://chromium.googlesource.com/chromiumos/chromite/+/master/lib/paygen/ +[DeltaArchiveManifest]: /update_metadata.proto#302 +[Signatures]: /update_metadata.proto#122 +[hard coded]: /update_engine.conf +[Manifest protobuf]: /update_metadata.proto +[update_payload]: /scripts/ +[Postinstall]: https://chromium.googlesource.com/chromiumos/platform2/+/master/installer/chromeos-postinst +[`update_engine` protobufs]: https://chromium.googlesource.com/chromiumos/platform2/+/master/system_api/dbus/update_engine/ +[Running unit tests similar to other platforms]: https://chromium.googlesource.com/chromiumos/docs/+/master/testing/running_unit_tests.md +[Nebraska]: https://chromium.googlesource.com/chromiumos/platform/dev-util/+/master/nebraska/ +[upstream branch]: https://chromium.googlesource.com/aosp/platform/system/update_engine/+/upstream +[`cros flash`]: https://chromium.googlesource.com/chromiumos/docs/+/master/cros_flash.md +[bsdiff]: https://android.googlesource.com/platform/external/bsdiff/+/master +[puffin]: https://android.googlesource.com/platform/external/puffin/+/master +[`update_engine_client`]: /update_engine_client.cc +[`brillo_update_payload`]: /scripts/brillo_update_payload +[`check_update_payload`]: /scripts/paycheck.py +[Dev Server]: https://chromium.googlesource.com/chromiumos/chromite/+/master/docs/devserver.md diff --git a/UpdateEngine.conf b/UpdateEngine.conf index 94900969..f9a66dce 100644 --- a/UpdateEngine.conf +++ b/UpdateEngine.conf @@ -1,5 +1,20 @@ <!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<!-- + 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. +!--> <busconfig> <policy user="root"> <allow own="org.chromium.UpdateEngine" /> @@ -26,7 +41,7 @@ send_member="ResetStatus"/> <allow send_destination="org.chromium.UpdateEngine" send_interface="org.chromium.UpdateEngineInterface" - send_member="GetStatus"/> + send_member="GetStatusAdvanced"/> <allow send_destination="org.chromium.UpdateEngine" send_interface="org.chromium.UpdateEngineInterface" send_member="RebootIfNeeded"/> @@ -66,15 +81,12 @@ <allow send_destination="org.chromium.UpdateEngine" send_interface="org.chromium.UpdateEngineInterface" send_member="GetLastAttemptError"/> - <allow send_destination="org.chromium.UpdateEngine" - send_interface="org.chromium.UpdateEngineInterface" - send_member="GetEolStatus"/> <allow send_interface="org.chromium.UpdateEngineLibcrosProxyResolvedInterface" /> </policy> <policy user="power"> <allow send_destination="org.chromium.UpdateEngine" send_interface="org.chromium.UpdateEngineInterface" - send_member="GetStatus"/> + send_member="GetStatusAdvanced"/> </policy> <policy user="dlcservice"> <allow send_destination="org.chromium.UpdateEngine" @@ -82,6 +94,12 @@ send_member="GetStatus"/> <allow send_destination="org.chromium.UpdateEngine" send_interface="org.chromium.UpdateEngineInterface" + send_member="GetStatusAdvanced"/> + <allow send_destination="org.chromium.UpdateEngine" + send_interface="org.chromium.UpdateEngineInterface" send_member="AttemptInstall"/> + <allow send_destination="org.chromium.UpdateEngine" + send_interface="org.chromium.UpdateEngineInterface" + send_member="SetDlcActiveValue"/> </policy> </busconfig> diff --git a/aosp/apex_handler_android.cc b/aosp/apex_handler_android.cc new file mode 100644 index 00000000..8beef966 --- /dev/null +++ b/aosp/apex_handler_android.cc @@ -0,0 +1,110 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <memory> +#include <utility> + +#include <base/files/file_util.h> + +#include <ApexProperties.sysprop.h> + +#include "update_engine/aosp/apex_handler_android.h" +#include "update_engine/common/utils.h" + +namespace chromeos_update_engine { + +namespace { + +android::apex::CompressedApexInfoList CreateCompressedApexInfoList( + const std::vector<ApexInfo>& apex_infos) { + android::apex::CompressedApexInfoList compressed_apex_info_list; + for (const auto& apex_info : apex_infos) { + if (!apex_info.is_compressed()) { + continue; + } + android::apex::CompressedApexInfo compressed_apex_info; + compressed_apex_info.moduleName = apex_info.package_name(); + compressed_apex_info.versionCode = apex_info.version(); + compressed_apex_info.decompressedSize = apex_info.decompressed_size(); + compressed_apex_info_list.apexInfos.emplace_back( + std::move(compressed_apex_info)); + } + return compressed_apex_info_list; +} + +} // namespace + +std::unique_ptr<ApexHandlerInterface> CreateApexHandler() { + if (android::sysprop::ApexProperties::updatable().value_or(false)) { + return std::make_unique<ApexHandlerAndroid>(); + } else { + return std::make_unique<FlattenedApexHandlerAndroid>(); + } +} + +android::base::Result<uint64_t> ApexHandlerAndroid::CalculateSize( + const std::vector<ApexInfo>& apex_infos) const { + // We might not need to decompress every APEX. Communicate with apexd to get + // accurate requirement. + auto apex_service = GetApexService(); + if (apex_service == nullptr) { + return android::base::Error() << "Failed to get hold of apexservice"; + } + + auto compressed_apex_info_list = CreateCompressedApexInfoList(apex_infos); + int64_t size_from_apexd; + auto result = apex_service->calculateSizeForCompressedApex( + compressed_apex_info_list, &size_from_apexd); + if (!result.isOk()) { + return android::base::Error() + << "Failed to get size required from apexservice"; + } + return size_from_apexd; +} + +bool ApexHandlerAndroid::AllocateSpace( + const std::vector<ApexInfo>& apex_infos) const { + auto apex_service = GetApexService(); + if (apex_service == nullptr) { + return false; + } + auto compressed_apex_info_list = CreateCompressedApexInfoList(apex_infos); + auto result = + apex_service->reserveSpaceForCompressedApex(compressed_apex_info_list); + return result.isOk(); +} + +android::sp<android::apex::IApexService> ApexHandlerAndroid::GetApexService() + const { + auto binder = android::defaultServiceManager()->waitForService( + android::String16("apexservice")); + if (binder == nullptr) { + return nullptr; + } + return android::interface_cast<android::apex::IApexService>(binder); +} + +android::base::Result<uint64_t> FlattenedApexHandlerAndroid::CalculateSize( + const std::vector<ApexInfo>& apex_infos) const { + return 0; +} + +bool FlattenedApexHandlerAndroid::AllocateSpace( + const std::vector<ApexInfo>& apex_infos) const { + return true; +} + +} // namespace chromeos_update_engine diff --git a/aosp/apex_handler_android.h b/aosp/apex_handler_android.h new file mode 100644 index 00000000..767f5618 --- /dev/null +++ b/aosp/apex_handler_android.h @@ -0,0 +1,53 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef SYSTEM_UPDATE_ENGINE_AOSP_APEX_HANDLER_ANDROID_H_ +#define SYSTEM_UPDATE_ENGINE_AOSP_APEX_HANDLER_ANDROID_H_ + +#include <memory> +#include <string> +#include <vector> + +#include <android/apex/IApexService.h> +#include <binder/IServiceManager.h> + +#include "update_engine/aosp/apex_handler_interface.h" +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { + +std::unique_ptr<ApexHandlerInterface> CreateApexHandler(); + +class ApexHandlerAndroid : virtual public ApexHandlerInterface { + public: + android::base::Result<uint64_t> CalculateSize( + const std::vector<ApexInfo>& apex_infos) const; + bool AllocateSpace(const std::vector<ApexInfo>& apex_infos) const; + + private: + android::sp<android::apex::IApexService> GetApexService() const; +}; + +class FlattenedApexHandlerAndroid : virtual public ApexHandlerInterface { + public: + android::base::Result<uint64_t> CalculateSize( + const std::vector<ApexInfo>& apex_infos) const; + bool AllocateSpace(const std::vector<ApexInfo>& apex_infos) const; +}; + +} // namespace chromeos_update_engine + +#endif // SYSTEM_UPDATE_ENGINE_AOSP_APEX_HANDLER_ANDROID_H_ diff --git a/aosp/apex_handler_android_unittest.cc b/aosp/apex_handler_android_unittest.cc new file mode 100644 index 00000000..847ccaad --- /dev/null +++ b/aosp/apex_handler_android_unittest.cc @@ -0,0 +1,102 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <utility> +#include <filesystem> + +#include "update_engine/aosp/apex_handler_android.h" + +#include <android-base/file.h> +#include <android-base/strings.h> +#include <gtest/gtest.h> + +using android::base::EndsWith; + +namespace chromeos_update_engine { + +namespace fs = std::filesystem; + +ApexInfo CreateApexInfo(const std::string& package_name, + int version, + bool is_compressed, + int decompressed_size) { + ApexInfo result; + result.set_package_name(package_name); + result.set_version(version); + result.set_is_compressed(is_compressed); + result.set_decompressed_size(decompressed_size); + return std::move(result); +} + +TEST(ApexHandlerAndroidTest, CalculateSizeUpdatableApex) { + ApexHandlerAndroid apex_handler; + std::vector<ApexInfo> apex_infos; + ApexInfo compressed_apex_1 = CreateApexInfo("sample1", 1, true, 1); + ApexInfo compressed_apex_2 = CreateApexInfo("sample2", 2, true, 2); + ApexInfo uncompressed_apex = CreateApexInfo("uncompressed", 1, false, 4); + apex_infos.push_back(compressed_apex_1); + apex_infos.push_back(compressed_apex_2); + apex_infos.push_back(uncompressed_apex); + auto result = apex_handler.CalculateSize(apex_infos); + ASSERT_TRUE(result.ok()); + ASSERT_EQ(*result, 3u); +} + +TEST(ApexHandlerAndroidTest, AllocateSpaceUpdatableApex) { + ApexHandlerAndroid apex_handler; + std::vector<ApexInfo> apex_infos; + ApexInfo compressed_apex_1 = CreateApexInfo("sample1", 1, true, 1); + ApexInfo compressed_apex_2 = CreateApexInfo("sample2", 2, true, 2); + ApexInfo uncompressed_apex = CreateApexInfo("uncompressed", 1, false, 4); + apex_infos.push_back(compressed_apex_1); + apex_infos.push_back(compressed_apex_2); + apex_infos.push_back(uncompressed_apex); + ASSERT_TRUE(apex_handler.AllocateSpace(apex_infos)); + + // Should be able to pass empty list + ASSERT_TRUE(apex_handler.AllocateSpace({})); +} + +TEST(ApexHandlerAndroidTest, CalculateSizeFlattenedApex) { + FlattenedApexHandlerAndroid apex_handler; + std::vector<ApexInfo> apex_infos; + ApexInfo compressed_apex_1 = CreateApexInfo("sample1", 1, true, 1); + ApexInfo compressed_apex_2 = CreateApexInfo("sample2", 2, true, 2); + ApexInfo uncompressed_apex = CreateApexInfo("uncompressed", 1, false, 4); + apex_infos.push_back(compressed_apex_1); + apex_infos.push_back(compressed_apex_2); + apex_infos.push_back(uncompressed_apex); + auto result = apex_handler.CalculateSize(apex_infos); + ASSERT_TRUE(result.ok()); + ASSERT_EQ(*result, 0u); +} + +TEST(ApexHandlerAndroidTest, AllocateSpaceFlattenedApex) { + FlattenedApexHandlerAndroid apex_handler; + std::vector<ApexInfo> apex_infos; + ApexInfo compressed_apex_1 = CreateApexInfo("sample1", 1, true, 1); + ApexInfo compressed_apex_2 = CreateApexInfo("sample2", 2, true, 2); + ApexInfo uncompressed_apex = CreateApexInfo("uncompressed", 1, false, 4); + apex_infos.push_back(compressed_apex_1); + apex_infos.push_back(compressed_apex_2); + apex_infos.push_back(uncompressed_apex); + ASSERT_TRUE(apex_handler.AllocateSpace(apex_infos)); + + // Should be able to pass empty list + ASSERT_TRUE(apex_handler.AllocateSpace({})); +} + +} // namespace chromeos_update_engine diff --git a/aosp/apex_handler_interface.h b/aosp/apex_handler_interface.h new file mode 100644 index 00000000..b9b6c96f --- /dev/null +++ b/aosp/apex_handler_interface.h @@ -0,0 +1,38 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef SYSTEM_UPDATE_ENGINE_AOSP_APEX_HANDLER_INTERFACE_H_ +#define SYSTEM_UPDATE_ENGINE_AOSP_APEX_HANDLER_INTERFACE_H_ + +#include <vector> + +#include <android-base/result.h> + +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { + +class ApexHandlerInterface { + public: + virtual ~ApexHandlerInterface() = default; + virtual android::base::Result<uint64_t> CalculateSize( + const std::vector<ApexInfo>& apex_infos) const = 0; + virtual bool AllocateSpace(const std::vector<ApexInfo>& apex_infos) const = 0; +}; + +} // namespace chromeos_update_engine + +#endif // SYSTEM_UPDATE_ENGINE_AOSP_APEX_HANDLER_INTERFACE_H_ diff --git a/binder_service_android.cc b/aosp/binder_service_android.cc index 6b8a5529..ed76c4a8 100644 --- a/binder_service_android.cc +++ b/aosp/binder_service_android.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/binder_service_android.h" +#include "update_engine/aosp/binder_service_android.h" #include <memory> @@ -24,6 +24,8 @@ #include <brillo/errors/error.h> #include <utils/String8.h> +#include "update_engine/aosp/binder_service_android_common.h" + using android::binder::Status; using android::os::IUpdateEngineCallback; using android::os::ParcelFileDescriptor; @@ -31,23 +33,6 @@ 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()}); -} - -vector<string> ToVecString(const vector<android::String16>& inp) { - vector<string> out; - out.reserve(inp.size()); - for (const auto& e : inp) { - out.emplace_back(android::String8{e}.string()); - } - return out; -} - -} // namespace - namespace chromeos_update_engine { BinderUpdateEngineAndroidService::BinderUpdateEngineAndroidService( diff --git a/binder_service_android.h b/aosp/binder_service_android.h index 5f282252..f41fbdf2 100644 --- a/binder_service_android.h +++ b/aosp/binder_service_android.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_BINDER_SERVICE_ANDROID_H_ -#define UPDATE_ENGINE_BINDER_SERVICE_ANDROID_H_ +#ifndef UPDATE_ENGINE_AOSP_BINDER_SERVICE_ANDROID_H_ +#define UPDATE_ENGINE_AOSP_BINDER_SERVICE_ANDROID_H_ #include <stdint.h> @@ -28,8 +28,8 @@ #include "android/os/BnUpdateEngine.h" #include "android/os/IUpdateEngineCallback.h" -#include "update_engine/service_delegate_android_interface.h" -#include "update_engine/service_observer_interface.h" +#include "update_engine/aosp/service_delegate_android_interface.h" +#include "update_engine/common/service_observer_interface.h" namespace chromeos_update_engine { @@ -96,4 +96,4 @@ class BinderUpdateEngineAndroidService : public android::os::BnUpdateEngine, } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_BINDER_SERVICE_ANDROID_H_ +#endif // UPDATE_ENGINE_AOSP_BINDER_SERVICE_ANDROID_H_ diff --git a/aosp/binder_service_android_common.h b/aosp/binder_service_android_common.h new file mode 100644 index 00000000..223b32ed --- /dev/null +++ b/aosp/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_AOSP_BINDER_SERVICE_ANDROID_COMMON_H_ +#define UPDATE_ENGINE_AOSP_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_AOSP_BINDER_SERVICE_ANDROID_COMMON_H_ diff --git a/aosp/binder_service_stable_android.cc b/aosp/binder_service_stable_android.cc new file mode 100644 index 00000000..17b35eea --- /dev/null +++ b/aosp/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/aosp/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/aosp/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/aosp/binder_service_stable_android.h b/aosp/binder_service_stable_android.h new file mode 100644 index 00000000..212afaa6 --- /dev/null +++ b/aosp/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_AOSP_BINDER_SERVICE_STABLE_ANDROID_H_ +#define UPDATE_ENGINE_AOSP_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/aosp/service_delegate_android_interface.h" +#include "update_engine/common/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_AOSP_BINDER_SERVICE_STABLE_ANDROID_H_ diff --git a/boot_control_android.cc b/aosp/boot_control_android.cc index ec2ca0f8..c1ac0d42 100644 --- a/boot_control_android.cc +++ b/aosp/boot_control_android.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/boot_control_android.h" +#include "update_engine/aosp/boot_control_android.h" #include <memory> #include <utility> @@ -25,13 +25,11 @@ #include <bootloader_message/bootloader_message.h> #include <brillo/message_loops/message_loop.h> +#include "update_engine/aosp/dynamic_partition_control_android.h" #include "update_engine/common/utils.h" -#include "update_engine/dynamic_partition_control_android.h" using std::string; -using android::dm::DmDeviceState; -using android::hardware::hidl_string; using android::hardware::Return; using android::hardware::boot::V1_0::BoolResult; using android::hardware::boot::V1_0::CommandResult; @@ -69,7 +67,8 @@ bool BootControlAndroid::Init() { LOG(INFO) << "Loaded boot control hidl hal."; - dynamic_control_ = std::make_unique<DynamicPartitionControlAndroid>(); + dynamic_control_ = + std::make_unique<DynamicPartitionControlAndroid>(GetCurrentSlot()); return true; } @@ -82,12 +81,24 @@ BootControlInterface::Slot BootControlAndroid::GetCurrentSlot() const { return module_->getCurrentSlot(); } +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 { - return dynamic_control_->GetPartitionDevice( - partition_name, slot, GetCurrentSlot(), device); + return GetPartitionDevice( + partition_name, slot, false /* not_in_payload */, device, nullptr); } bool BootControlAndroid::IsSlotBootable(Slot slot) const { @@ -171,4 +182,12 @@ BootControlAndroid::GetDynamicPartitionControl() { return dynamic_control_.get(); } +std::optional<PartitionDevice> BootControlAndroid::GetPartitionDevice( + const std::string& partition_name, + uint32_t slot, + uint32_t current_slot, + bool not_in_payload) const { + return dynamic_control_->GetPartitionDevice( + partition_name, slot, current_slot, not_in_payload); +} } // namespace chromeos_update_engine diff --git a/boot_control_android.h b/aosp/boot_control_android.h index 0b042e3d..926023a1 100644 --- a/boot_control_android.h +++ b/aosp/boot_control_android.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_BOOT_CONTROL_ANDROID_H_ -#define UPDATE_ENGINE_BOOT_CONTROL_ANDROID_H_ +#ifndef UPDATE_ENGINE_AOSP_BOOT_CONTROL_ANDROID_H_ +#define UPDATE_ENGINE_AOSP_BOOT_CONTROL_ANDROID_H_ #include <map> #include <memory> @@ -24,9 +24,9 @@ #include <android/hardware/boot/1.0/IBootControl.h> #include <liblp/builder.h> +#include "update_engine/aosp/dynamic_partition_control_android.h" #include "update_engine/common/boot_control.h" #include "update_engine/common/dynamic_partition_control_interface.h" -#include "update_engine/dynamic_partition_control_android.h" namespace chromeos_update_engine { @@ -44,6 +44,16 @@ class BootControlAndroid : public BootControlInterface { // BootControlInterface overrides. unsigned int GetNumSlots() const override; BootControlInterface::Slot GetCurrentSlot() const override; + std::optional<PartitionDevice> GetPartitionDevice( + const std::string& partition_name, + uint32_t slot, + uint32_t current_slot, + bool not_in_payload = false) 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; @@ -65,4 +75,4 @@ class BootControlAndroid : public BootControlInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_BOOT_CONTROL_ANDROID_H_ +#endif // UPDATE_ENGINE_AOSP_BOOT_CONTROL_ANDROID_H_ diff --git a/cleanup_previous_update_action.cc b/aosp/cleanup_previous_update_action.cc index 9f84c3bd..53cb9933 100644 --- a/cleanup_previous_update_action.cc +++ b/aosp/cleanup_previous_update_action.cc @@ -13,13 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. // -#include "update_engine/cleanup_previous_update_action.h" +#include "update_engine/aosp/cleanup_previous_update_action.h" #include <chrono> // NOLINT(build/c++11) -- for merge times #include <functional> #include <string> #include <type_traits> +#include <android-base/chrono_utils.h> #include <android-base/properties.h> #include <base/bind.h> @@ -31,7 +32,7 @@ #include "update_engine/payload_consumer/delta_performer.h" using android::base::GetBoolProperty; -using android::snapshot::SnapshotManager; +using android::snapshot::ISnapshotManager; using android::snapshot::SnapshotMergeStats; using android::snapshot::UpdateState; using brillo::MessageLoop; @@ -56,7 +57,7 @@ namespace chromeos_update_engine { CleanupPreviousUpdateAction::CleanupPreviousUpdateAction( PrefsInterface* prefs, BootControlInterface* boot_control, - android::snapshot::SnapshotManager* snapshot, + android::snapshot::ISnapshotManager* snapshot, CleanupPreviousUpdateActionDelegateInterface* delegate) : prefs_(prefs), boot_control_(boot_control), @@ -65,32 +66,30 @@ CleanupPreviousUpdateAction::CleanupPreviousUpdateAction( running_(false), cancel_failed_(false), last_percentage_(0), - merge_stats_(SnapshotMergeStats::GetInstance(*snapshot)) {} + merge_stats_(nullptr) {} + +CleanupPreviousUpdateAction::~CleanupPreviousUpdateAction() { + StopActionInternal(); +} void CleanupPreviousUpdateAction::PerformAction() { - ResumeAction(); + StartActionInternal(); } void CleanupPreviousUpdateAction::TerminateProcessing() { - SuspendAction(); + StopActionInternal(); } 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; + StopActionInternal(); } void CleanupPreviousUpdateAction::ActionCompleted(ErrorCode error_code) { - running_ = false; + StopActionInternal(); ReportMergeStats(); metadata_device_ = nullptr; } @@ -103,7 +102,52 @@ std::string CleanupPreviousUpdateAction::StaticType() { return "CleanupPreviousUpdateAction"; } +// This function is called at the beginning of all delayed functions. By +// resetting |scheduled_task_|, the delayed function acknowledges that the task +// has already been executed, therefore there's no need to cancel it in the +// future. This avoids StopActionInternal() from resetting task IDs in an +// unexpected way because task IDs could be reused. +void CleanupPreviousUpdateAction::AcknowledgeTaskExecuted() { + if (scheduled_task_ != MessageLoop::kTaskIdNull) { + LOG(INFO) << "Executing task " << scheduled_task_; + } + scheduled_task_ = MessageLoop::kTaskIdNull; +} + +// Check that scheduled_task_ is a valid task ID. Otherwise, terminate the +// action. +void CleanupPreviousUpdateAction::CheckTaskScheduled(std::string_view name) { + if (scheduled_task_ == MessageLoop::kTaskIdNull) { + LOG(ERROR) << "Unable to schedule " << name; + processor_->ActionComplete(this, ErrorCode::kError); + } else { + LOG(INFO) << "CleanupPreviousUpdateAction scheduled task ID " + << scheduled_task_ << " for " << name; + } +} + +void CleanupPreviousUpdateAction::StopActionInternal() { + LOG(INFO) << "Stopping/suspending/completing CleanupPreviousUpdateAction"; + running_ = false; + + if (scheduled_task_ != MessageLoop::kTaskIdNull) { + if (MessageLoop::current()->CancelTask(scheduled_task_)) { + LOG(INFO) << "CleanupPreviousUpdateAction cancelled pending task ID " + << scheduled_task_; + } else { + LOG(ERROR) << "CleanupPreviousUpdateAction unable to cancel task ID " + << scheduled_task_; + } + } + scheduled_task_ = MessageLoop::kTaskIdNull; +} + void CleanupPreviousUpdateAction::StartActionInternal() { + CHECK(prefs_); + CHECK(boot_control_); + + LOG(INFO) << "Starting/resuming CleanupPreviousUpdateAction"; + running_ = true; // Do nothing on non-VAB device. if (!boot_control_->GetDynamicPartitionControl() ->GetVirtualAbFeatureFlag() @@ -111,21 +155,25 @@ void CleanupPreviousUpdateAction::StartActionInternal() { processor_->ActionComplete(this, ErrorCode::kSuccess); return; } - // SnapshotManager is only available on VAB devices. - CHECK(snapshot_); + // 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( + scheduled_task_ = MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&CleanupPreviousUpdateAction::WaitBootCompletedOrSchedule, base::Unretained(this)), kCheckBootCompletedInterval); + CheckTaskScheduled("WaitBootCompleted"); } void CleanupPreviousUpdateAction::WaitBootCompletedOrSchedule() { + AcknowledgeTaskExecuted(); TEST_AND_RETURN(running_); if (!kIsRecovery && !android::base::GetBoolProperty(kBootCompletedProp, false)) { @@ -134,21 +182,27 @@ void CleanupPreviousUpdateAction::WaitBootCompletedOrSchedule() { return; } + auto boot_time = std::chrono::duration_cast<std::chrono::milliseconds>( + android::base::boot_clock::now().time_since_epoch()); + merge_stats_->set_boot_complete_time_ms(boot_time.count()); + LOG(INFO) << "Boot completed, waiting on markBootSuccessful()"; CheckSlotMarkedSuccessfulOrSchedule(); } void CleanupPreviousUpdateAction::ScheduleWaitMarkBootSuccessful() { TEST_AND_RETURN(running_); - MessageLoop::current()->PostDelayedTask( + scheduled_task_ = MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind( &CleanupPreviousUpdateAction::CheckSlotMarkedSuccessfulOrSchedule, base::Unretained(this)), kCheckSlotMarkedSuccessfulInterval); + CheckTaskScheduled("WaitMarkBootSuccessful"); } void CleanupPreviousUpdateAction::CheckSlotMarkedSuccessfulOrSchedule() { + AcknowledgeTaskExecuted(); TEST_AND_RETURN(running_); if (!kIsRecovery && !boot_control_->IsSlotMarkedSuccessful(boot_control_->GetCurrentSlot())) { @@ -211,19 +265,22 @@ void CleanupPreviousUpdateAction::CheckSlotMarkedSuccessfulOrSchedule() { void CleanupPreviousUpdateAction::ScheduleWaitForMerge() { TEST_AND_RETURN(running_); - MessageLoop::current()->PostDelayedTask( + scheduled_task_ = MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&CleanupPreviousUpdateAction::WaitForMergeOrSchedule, base::Unretained(this)), kWaitForMergeInterval); + CheckTaskScheduled("WaitForMerge"); } void CleanupPreviousUpdateAction::WaitForMergeOrSchedule() { + AcknowledgeTaskExecuted(); TEST_AND_RETURN(running_); + auto update_uses_compression = snapshot_->UpdateUsesCompression(); auto state = snapshot_->ProcessUpdateState( std::bind(&CleanupPreviousUpdateAction::OnMergePercentageUpdate, this), std::bind(&CleanupPreviousUpdateAction::BeforeCancel, this)); - merge_stats_->set_state(state); + merge_stats_->set_state(state, update_uses_compression); switch (state) { case UpdateState::None: { @@ -267,6 +324,7 @@ void CleanupPreviousUpdateAction::WaitForMergeOrSchedule() { case UpdateState::MergeFailed: { LOG(ERROR) << "Merge failed. Device may be corrupted."; + merge_stats_->set_merge_failure_code(snapshot_->ReadMergeFailureCode()); processor_->ActionComplete(this, ErrorCode::kDeviceCorrupted); return; } @@ -344,16 +402,29 @@ void CleanupPreviousUpdateAction::InitiateMergeAndWait() { return; } - uint64_t cow_file_size; - if (snapshot_->InitiateMerge(&cow_file_size)) { - merge_stats_->set_cow_file_size(cow_file_size); + snapshot_->UpdateCowStats(merge_stats_); + + auto merge_start_time = std::chrono::duration_cast<std::chrono::milliseconds>( + android::base::boot_clock::now().time_since_epoch()); + merge_stats_->set_boot_complete_to_merge_start_time_ms( + merge_start_time.count() - merge_stats_->boot_complete_time_ms()); + + auto source_build_fingerprint = snapshot_->ReadSourceBuildFingerprint(); + merge_stats_->set_source_build_fingerprint(source_build_fingerprint); + + if (!merge_stats_->WriteState()) { + LOG(ERROR) << "Failed to write merge stats; record may be unreliable if " + "merge is interrupted."; + } + + if (snapshot_->InitiateMerge()) { WaitForMergeOrSchedule(); return; } LOG(WARNING) << "InitiateMerge failed."; auto state = snapshot_->GetUpdateState(); - merge_stats_->set_state(state); + merge_stats_->set_state(state, snapshot_->UpdateUsesCompression()); 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, @@ -406,6 +477,16 @@ void CleanupPreviousUpdateAction::ReportMergeStats() { bool vab_retrofit = boot_control_->GetDynamicPartitionControl() ->GetVirtualAbFeatureFlag() .IsRetrofit(); + bool vab_compression_enabled = boot_control_->GetDynamicPartitionControl() + ->GetVirtualAbCompressionFeatureFlag() + .IsEnabled(); + // The snapshot has been merged, so we can no longer call + // DynamicPartitionControlInterface::UpdateUsesSnapshotCompression. + // However, we have saved the flag in the snapshot report. + bool vab_compression_used = report.compression_enabled(); + + auto target_build_fingerprint = + android::base::GetProperty("ro.build.fingerprint", ""); LOG(INFO) << "Reporting merge stats: " << android::snapshot::UpdateState_Name(report.state()) << " in " @@ -417,7 +498,16 @@ void CleanupPreviousUpdateAction::ReportMergeStats() { static_cast<int64_t>(passed_ms.count()), static_cast<int32_t>(report.resume_count()), vab_retrofit, - static_cast<int64_t>(report.cow_file_size())); + static_cast<int64_t>(report.cow_file_size()), + vab_compression_enabled, + vab_compression_used, + report.total_cow_size_bytes(), + report.estimated_cow_size_bytes(), + report.boot_complete_time_ms(), + report.boot_complete_to_merge_start_time_ms(), + static_cast<int32_t>(report.merge_failure_code()), + report.source_build_fingerprint().c_str(), + target_build_fingerprint.c_str()); #endif } diff --git a/cleanup_previous_update_action.h b/aosp/cleanup_previous_update_action.h index 91e08b01..b93c5575 100644 --- a/cleanup_previous_update_action.h +++ b/aosp/cleanup_previous_update_action.h @@ -14,12 +14,13 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_H_ -#define UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_H_ +#ifndef UPDATE_ENGINE_AOSP_CLEANUP_PREVIOUS_UPDATE_ACTION_H_ +#define UPDATE_ENGINE_AOSP_CLEANUP_PREVIOUS_UPDATE_ACTION_H_ #include <chrono> // NOLINT(build/c++11) -- for merge times #include <memory> #include <string> +#include <string_view> #include <brillo/message_loops/message_loop.h> #include <libsnapshot/snapshot.h> @@ -49,8 +50,9 @@ class CleanupPreviousUpdateAction : public Action<CleanupPreviousUpdateAction> { CleanupPreviousUpdateAction( PrefsInterface* prefs, BootControlInterface* boot_control, - android::snapshot::SnapshotManager* snapshot, + android::snapshot::ISnapshotManager* snapshot, CleanupPreviousUpdateActionDelegateInterface* delegate); + ~CleanupPreviousUpdateAction(); void PerformAction() override; void SuspendAction() override; @@ -67,14 +69,20 @@ class CleanupPreviousUpdateAction : public Action<CleanupPreviousUpdateAction> { private: PrefsInterface* prefs_; BootControlInterface* boot_control_; - android::snapshot::SnapshotManager* snapshot_; + 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::SnapshotMergeStats* merge_stats_; + android::snapshot::ISnapshotMergeStats* merge_stats_; + brillo::MessageLoop::TaskId scheduled_task_{brillo::MessageLoop::kTaskIdNull}; + // Helpers for task management. + void AcknowledgeTaskExecuted(); + void CheckTaskScheduled(std::string_view name); + + void StopActionInternal(); void StartActionInternal(); void ScheduleWaitBootCompleted(); void WaitBootCompletedOrSchedule(); @@ -92,4 +100,4 @@ class CleanupPreviousUpdateAction : public Action<CleanupPreviousUpdateAction> { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_H_ +#endif // UPDATE_ENGINE_AOSP_CLEANUP_PREVIOUS_UPDATE_ACTION_H_ diff --git a/aosp/cleanup_previous_update_action_unittest.cc b/aosp/cleanup_previous_update_action_unittest.cc new file mode 100644 index 00000000..0d2b4e6a --- /dev/null +++ b/aosp/cleanup_previous_update_action_unittest.cc @@ -0,0 +1,174 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <algorithm> + +#include <brillo/message_loops/fake_message_loop.h> +#include <gtest/gtest.h> +#include <libsnapshot/snapshot.h> +#include <libsnapshot/mock_snapshot.h> +#include <libsnapshot/mock_snapshot_merge_stats.h> + +#include "update_engine/aosp/cleanup_previous_update_action.h" +#include "update_engine/common/mock_boot_control.h" +#include "update_engine/common/mock_dynamic_partition_control.h" +#include "update_engine/common/mock_prefs.h" + +namespace chromeos_update_engine { + +using android::snapshot::AutoDevice; +using android::snapshot::MockSnapshotManager; +using android::snapshot::MockSnapshotMergeStats; +using android::snapshot::UpdateState; +using testing::_; +using testing::AtLeast; +using testing::Return; + +class MockCleanupPreviousUpdateActionDelegate final + : public CleanupPreviousUpdateActionDelegateInterface { + MOCK_METHOD(void, OnCleanupProgressUpdate, (double), (override)); +}; + +class MockActionProcessor : public ActionProcessor { + public: + MOCK_METHOD(void, ActionComplete, (AbstractAction*, ErrorCode), (override)); +}; + +class MockAutoDevice : public AutoDevice { + public: + explicit MockAutoDevice(std::string name) : AutoDevice(name) {} + ~MockAutoDevice() = default; +}; + +class CleanupPreviousUpdateActionTest : public ::testing::Test { + public: + void SetUp() override { + ON_CALL(boot_control_, GetDynamicPartitionControl()) + .WillByDefault(Return(&dynamic_control_)); + ON_CALL(boot_control_, GetCurrentSlot()).WillByDefault(Return(0)); + ON_CALL(mock_snapshot_, GetSnapshotMergeStatsInstance()) + .WillByDefault(Return(&mock_stats_)); + action_.SetProcessor(&mock_processor_); + loop_.SetAsCurrent(); + } + + constexpr static FeatureFlag LAUNCH{FeatureFlag::Value::LAUNCH}; + constexpr static FeatureFlag NONE{FeatureFlag::Value::NONE}; + MockSnapshotManager mock_snapshot_; + MockPrefs mock_prefs_; + MockBootControl boot_control_; + MockDynamicPartitionControl dynamic_control_{}; + MockCleanupPreviousUpdateActionDelegate mock_delegate_; + MockSnapshotMergeStats mock_stats_; + MockActionProcessor mock_processor_; + brillo::FakeMessageLoop loop_{nullptr}; + CleanupPreviousUpdateAction action_{ + &mock_prefs_, &boot_control_, &mock_snapshot_, &mock_delegate_}; +}; + +TEST_F(CleanupPreviousUpdateActionTest, NonVabTest) { + // Since VAB isn't even enabled, |GetSnapshotMergeStatsInstance| shouldn't be + // called at all + EXPECT_CALL(mock_snapshot_, GetSnapshotMergeStatsInstance()).Times(0); + EXPECT_CALL(dynamic_control_, GetVirtualAbFeatureFlag()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(NONE)); + action_.PerformAction(); +} + +TEST_F(CleanupPreviousUpdateActionTest, VABSlotSuccessful) { + // Expectaion: if VABC is enabled, Clenup action should call + // |SnapshotMergeStats::Start()| to start merge, and wait for it to finish + EXPECT_CALL(mock_snapshot_, GetSnapshotMergeStatsInstance()) + .Times(AtLeast(1)); + EXPECT_CALL(mock_snapshot_, EnsureMetadataMounted()) + .Times(AtLeast(1)) + .WillRepeatedly( + []() { return std::make_unique<MockAutoDevice>("mock_device"); }); + EXPECT_CALL(dynamic_control_, GetVirtualAbFeatureFlag()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(LAUNCH)); + // CleanupPreviousUpdateAction should use whatever slot returned by + // |GetCurrentSlot()| + EXPECT_CALL(boot_control_, GetCurrentSlot()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(1)); + EXPECT_CALL(boot_control_, IsSlotMarkedSuccessful(1)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(mock_snapshot_, ProcessUpdateState(_, _)) + .Times(AtLeast(2)) + .WillOnce(Return(UpdateState::Merging)) + .WillRepeatedly(Return(UpdateState::MergeCompleted)); + EXPECT_CALL(mock_stats_, Start()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(mock_processor_, ActionComplete(&action_, ErrorCode::kSuccess)) + .Times(1); + action_.PerformAction(); + while (loop_.PendingTasks()) { + ASSERT_TRUE(loop_.RunOnce(true)); + } +} + +TEST_F(CleanupPreviousUpdateActionTest, VabSlotNotReady) { + // Cleanup action should repeatly query boot control until the slot is marked + // successful. + static constexpr auto MAX_TIMEPOINT = + std::chrono::steady_clock::time_point::max(); + EXPECT_CALL(mock_snapshot_, GetSnapshotMergeStatsInstance()) + .Times(AtLeast(1)); + EXPECT_CALL(mock_snapshot_, EnsureMetadataMounted()) + .Times(AtLeast(1)) + .WillRepeatedly( + []() { return std::make_unique<MockAutoDevice>("mock_device"); }); + EXPECT_CALL(dynamic_control_, GetVirtualAbFeatureFlag()) + .Times(AtLeast(1)) + .WillRepeatedly(Return(LAUNCH)); + auto slot_success_time = MAX_TIMEPOINT; + auto merge_start_time = MAX_TIMEPOINT; + EXPECT_CALL(boot_control_, IsSlotMarkedSuccessful(_)) + .Times(AtLeast(3)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce([&slot_success_time]() { + slot_success_time = + std::min(slot_success_time, std::chrono::steady_clock::now()); + return true; + }); + + EXPECT_CALL(mock_stats_, Start()) + .Times(1) + .WillRepeatedly([&merge_start_time]() { + merge_start_time = + std::min(merge_start_time, std::chrono::steady_clock::now()); + return true; + }); + + EXPECT_CALL(mock_snapshot_, ProcessUpdateState(_, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(UpdateState::MergeCompleted)); + EXPECT_CALL(mock_processor_, ActionComplete(&action_, ErrorCode::kSuccess)) + .Times(1); + action_.PerformAction(); + while (loop_.PendingTasks()) { + ASSERT_TRUE(loop_.RunOnce(true)); + } + ASSERT_LT(slot_success_time, merge_start_time) + << "Merge should not be started until slot is marked successful"; +} + +} // namespace chromeos_update_engine diff --git a/aosp/cow_converter.cc b/aosp/cow_converter.cc new file mode 100644 index 00000000..8c641b8d --- /dev/null +++ b/aosp/cow_converter.cc @@ -0,0 +1,131 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <stdio.h> +#include <string.h> + +#include <cstdint> +#include <cstdio> +#include <memory> + +#include <sys/fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> + +#include <base/files/file_path.h> +#include <libsnapshot/cow_writer.h> + +#include "update_engine/common/cow_operation_convert.h" +#include "update_engine/common/utils.h" +#include "update_engine/payload_consumer/file_descriptor.h" +#include "update_engine/payload_consumer/payload_metadata.h" +#include "update_engine/payload_generator/cow_size_estimator.h" +#include "update_engine/update_metadata.pb.h" + +using android::snapshot::CowWriter; + +namespace chromeos_update_engine { + +bool ProcessPartition(const chromeos_update_engine::PartitionUpdate& partition, + const char* image_dir, + size_t block_size) { + base::FilePath img_dir{image_dir}; + auto target_img = img_dir.Append(partition.partition_name() + ".img"); + auto output_cow = img_dir.Append(partition.partition_name() + ".cow"); + FileDescriptorPtr target_img_fd = std::make_shared<EintrSafeFileDescriptor>(); + if (!target_img_fd->Open(target_img.value().c_str(), O_RDONLY)) { + PLOG(ERROR) << "Failed to open " << target_img.value(); + return false; + } + android::base::unique_fd output_fd{ + open(output_cow.value().c_str(), O_RDWR | O_CREAT, 0744)}; + if (output_fd < 0) { + PLOG(ERROR) << "Failed to open " << output_cow.value(); + return false; + } + + android::snapshot::CowWriter cow_writer{ + {.block_size = static_cast<uint32_t>(block_size), .compression = "gz"}}; + TEST_AND_RETURN_FALSE(cow_writer.Initialize(output_fd)); + TEST_AND_RETURN_FALSE(CowDryRun(target_img_fd, + partition.operations(), + partition.merge_operations(), + block_size, + &cow_writer)); + TEST_AND_RETURN_FALSE(cow_writer.Finalize()); + return true; +} + +} // namespace chromeos_update_engine + +using chromeos_update_engine::MetadataParseResult; +using chromeos_update_engine::PayloadMetadata; + +int main(int argc, const char* argv[]) { + if (argc != 3) { + printf("Usage: %s <payload.bin> <extracted target_file>\n", argv[0]); + return -1; + } + const char* payload_path = argv[1]; + const char* images_dir = argv[2]; + int payload_fd = open(payload_path, O_RDONLY); + if (payload_fd < 0) { + PLOG(ERROR) << "Failed to open payload file:"; + return 1; + } + chromeos_update_engine::ScopedFdCloser closer{&payload_fd}; + auto payload_size = chromeos_update_engine::utils::FileSize(payload_fd); + if (payload_size <= 0) { + PLOG(ERROR) + << "Couldn't determine size of payload file, or payload file is empty"; + return 2; + } + + PayloadMetadata payload_metadata; + auto payload = static_cast<unsigned char*>( + mmap(nullptr, payload_size, PROT_READ, MAP_PRIVATE, payload_fd, 0)); + + // C++ dark magic to ensure that |payload| is properly deallocated once the + // program exits. + auto munmap_deleter = [payload_size](auto payload) { + munmap(payload, payload_size); + }; + std::unique_ptr<unsigned char, decltype(munmap_deleter)> munmapper{ + payload, munmap_deleter}; + + if (payload == nullptr) { + PLOG(ERROR) << "Failed to mmap() payload file"; + return 3; + } + if (payload_metadata.ParsePayloadHeader(payload, payload_size, nullptr) != + chromeos_update_engine::MetadataParseResult::kSuccess) { + LOG(ERROR) << "Payload header parse failed!"; + return 4; + } + chromeos_update_engine::DeltaArchiveManifest manifest; + if (!payload_metadata.GetManifest(payload, payload_size, &manifest)) { + LOG(ERROR) << "Failed to parse manifest!"; + return 5; + } + + for (const auto& partition : manifest.partitions()) { + LOG(INFO) << partition.partition_name(); + if (!ProcessPartition(partition, images_dir, manifest.block_size())) { + return 6; + } + } + return 0; +} diff --git a/aosp/daemon_android.cc b/aosp/daemon_android.cc new file mode 100644 index 00000000..c102e3b0 --- /dev/null +++ b/aosp/daemon_android.cc @@ -0,0 +1,74 @@ +// +// Copyright (C) 2015 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/aosp/daemon_android.h" + +#include <sysexits.h> + +#include <binderwrapper/binder_wrapper.h> + +#include "update_engine/aosp/daemon_state_android.h" + +using std::unique_ptr; + +namespace chromeos_update_engine { + +unique_ptr<DaemonBase> DaemonBase::CreateInstance() { + return std::make_unique<DaemonAndroid>(); +} + +int DaemonAndroid::OnInit() { + // Register the |subprocess_| singleton with this Daemon as the signal + // handler. + subprocess_.Init(this); + + int exit_code = brillo::Daemon::OnInit(); + if (exit_code != EX_OK) + return exit_code; + + android::BinderWrapper::Create(); + binder_watcher_.Init(); + + DaemonStateAndroid* daemon_state_android = new DaemonStateAndroid(); + daemon_state_.reset(daemon_state_android); + 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()}; + 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; +} + +} // namespace chromeos_update_engine diff --git a/aosp/daemon_android.h b/aosp/daemon_android.h new file mode 100644 index 00000000..38a86891 --- /dev/null +++ b/aosp/daemon_android.h @@ -0,0 +1,58 @@ +// +// Copyright (C) 2015 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_AOSP_DAEMON_ANDROID_H_ +#define UPDATE_ENGINE_AOSP_DAEMON_ANDROID_H_ + +#include <memory> + +#include <brillo/binder_watcher.h> + +#include "update_engine/aosp/binder_service_android.h" +#include "update_engine/aosp/binder_service_stable_android.h" +#include "update_engine/common/daemon_base.h" +#include "update_engine/common/daemon_state_interface.h" +#include "update_engine/common/subprocess.h" + +namespace chromeos_update_engine { + +class DaemonAndroid : public DaemonBase { + public: + DaemonAndroid() = default; + + protected: + int OnInit() override; + + private: + // The Subprocess singleton class requires a |brillo::MessageLoop| in the + // current thread, so we need to initialize it from this class instead of + // the main() function. + Subprocess subprocess_; + + 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. + std::unique_ptr<DaemonStateInterface> daemon_state_; + + DISALLOW_COPY_AND_ASSIGN(DaemonAndroid); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_AOSP_DAEMON_ANDROID_H_ diff --git a/daemon_state_android.cc b/aosp/daemon_state_android.cc index c9c09b88..da49080c 100644 --- a/daemon_state_android.cc +++ b/aosp/daemon_state_android.cc @@ -14,15 +14,16 @@ // limitations under the License. // -#include "update_engine/daemon_state_android.h" +#include "update_engine/aosp/daemon_state_android.h" #include <base/logging.h> +#include "update_engine/aosp/apex_handler_android.h" +#include "update_engine/aosp/update_attempter_android.h" #include "update_engine/common/boot_control.h" #include "update_engine/common/boot_control_stub.h" #include "update_engine/common/hardware.h" #include "update_engine/common/prefs.h" -#include "update_engine/update_attempter_android.h" namespace chromeos_update_engine { @@ -45,17 +46,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. @@ -64,8 +65,11 @@ bool DaemonStateAndroid::Initialize() { certificate_checker_->Init(); // Initialize the UpdateAttempter before the UpdateManager. - update_attempter_.reset(new UpdateAttempterAndroid( - this, prefs_.get(), boot_control_.get(), hardware_.get())); + update_attempter_.reset(new UpdateAttempterAndroid(this, + prefs_.get(), + boot_control_.get(), + hardware_.get(), + CreateApexHandler())); return true; } diff --git a/daemon_state_android.h b/aosp/daemon_state_android.h index 928a14eb..dea3a23f 100644 --- a/daemon_state_android.h +++ b/aosp/daemon_state_android.h @@ -14,20 +14,20 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_DAEMON_STATE_ANDROID_H_ -#define UPDATE_ENGINE_DAEMON_STATE_ANDROID_H_ +#ifndef UPDATE_ENGINE_AOSP_DAEMON_STATE_ANDROID_H_ +#define UPDATE_ENGINE_AOSP_DAEMON_STATE_ANDROID_H_ #include <memory> #include <set> +#include "update_engine/aosp/service_delegate_android_interface.h" +#include "update_engine/aosp/update_attempter_android.h" #include "update_engine/certificate_checker.h" #include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/daemon_state_interface.h" #include "update_engine/common/hardware_interface.h" #include "update_engine/common/prefs_interface.h" -#include "update_engine/daemon_state_interface.h" -#include "update_engine/service_delegate_android_interface.h" -#include "update_engine/service_observer_interface.h" -#include "update_engine/update_attempter_android.h" +#include "update_engine/common/service_observer_interface.h" namespace chromeos_update_engine { @@ -73,4 +73,4 @@ class DaemonStateAndroid : public DaemonStateInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_DAEMON_STATE_ANDROID_H_ +#endif // UPDATE_ENGINE_AOSP_DAEMON_STATE_ANDROID_H_ diff --git a/dynamic_partition_control_android.cc b/aosp/dynamic_partition_control_android.cc index 708380a8..538b57ce 100644 --- a/dynamic_partition_control_android.cc +++ b/aosp/dynamic_partition_control_android.cc @@ -14,13 +14,17 @@ // limitations under the License. // -#include "update_engine/dynamic_partition_control_android.h" +#include "update_engine/aosp/dynamic_partition_control_android.h" +#include <algorithm> #include <chrono> // NOLINT(build/c++11) - using libsnapshot / liblp API +#include <cstdint> #include <map> #include <memory> #include <set> #include <string> +#include <string_view> +#include <utility> #include <vector> #include <android-base/properties.h> @@ -28,18 +32,25 @@ #include <base/files/file_util.h> #include <base/logging.h> #include <base/strings/string_util.h> +#include <base/strings/stringprintf.h> #include <bootloader_message/bootloader_message.h> #include <fs_mgr.h> #include <fs_mgr_dm_linear.h> #include <fs_mgr_overlayfs.h> #include <libavb/libavb.h> #include <libdm/dm.h> +#include <liblp/liblp.h> +#include <libsnapshot/cow_writer.h> #include <libsnapshot/snapshot.h> +#include <libsnapshot/snapshot_stub.h> -#include "update_engine/cleanup_previous_update_action.h" +#include "update_engine/aosp/cleanup_previous_update_action.h" +#include "update_engine/aosp/dynamic_partition_utils.h" #include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/dynamic_partition_control_interface.h" +#include "update_engine/common/platform_constants.h" #include "update_engine/common/utils.h" -#include "update_engine/dynamic_partition_utils.h" +#include "update_engine/payload_consumer/cow_writer_file_descriptor.h" #include "update_engine/payload_consumer/delta_performer.h" using android::base::GetBoolProperty; @@ -58,7 +69,9 @@ 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; +using base::StringPrintf; namespace chromeos_update_engine { @@ -67,6 +80,14 @@ constexpr char kRetrfoitDynamicPartitions[] = "ro.boot.dynamic_partitions_retrofit"; constexpr char kVirtualAbEnabled[] = "ro.virtual_ab.enabled"; constexpr char kVirtualAbRetrofit[] = "ro.virtual_ab.retrofit"; +constexpr char kVirtualAbCompressionEnabled[] = + "ro.virtual_ab.compression.enabled"; + +// Currently, android doesn't have a retrofit prop for VAB Compression. However, +// struct FeatureFlag forces us to determine if a feature is 'retrofit'. So this +// is here just to simplify code. Replace it with real retrofit prop name once +// there is one. +constexpr char kVirtualAbCompressionRetrofit[] = ""; constexpr char kPostinstallFstabPrefix[] = "ro.postinstall.fstab.prefix"; // Map timeout for dynamic partitions. constexpr std::chrono::milliseconds kMapTimeout{1000}; @@ -74,19 +95,15 @@ constexpr std::chrono::milliseconds kMapTimeout{1000}; // 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() { Cleanup(); } static FeatureFlag GetFeatureFlag(const char* enable_prop, const char* retrofit_prop) { - bool retrofit = GetBoolProperty(retrofit_prop, false); + // Default retrofit to false if retrofit_prop is empty. + bool retrofit = retrofit_prop && retrofit_prop[0] != '\0' && + GetBoolProperty(retrofit_prop, false); bool enabled = GetBoolProperty(enable_prop, false); if (retrofit && !enabled) { LOG(ERROR) << retrofit_prop << " is true but " << enable_prop @@ -102,14 +119,20 @@ static FeatureFlag GetFeatureFlag(const char* enable_prop, return FeatureFlag(FeatureFlag::Value::NONE); } -DynamicPartitionControlAndroid::DynamicPartitionControlAndroid() +DynamicPartitionControlAndroid::DynamicPartitionControlAndroid( + uint32_t source_slot) : dynamic_partitions_( GetFeatureFlag(kUseDynamicPartitions, kRetrfoitDynamicPartitions)), - virtual_ab_(GetFeatureFlag(kVirtualAbEnabled, kVirtualAbRetrofit)) { + virtual_ab_(GetFeatureFlag(kVirtualAbEnabled, kVirtualAbRetrofit)), + virtual_ab_compression_(GetFeatureFlag(kVirtualAbCompressionEnabled, + kVirtualAbCompressionRetrofit)), + source_slot_(source_slot) { if (GetVirtualAbFeatureFlag().IsEnabled()) { snapshot_ = SnapshotManager::New(); - CHECK(snapshot_ != nullptr) << "Cannot initialize SnapshotManager."; + } else { + snapshot_ = SnapshotManagerStub::New(); } + CHECK(snapshot_ != nullptr) << "Cannot initialize SnapshotManager."; } FeatureFlag DynamicPartitionControlAndroid::GetDynamicPartitionsFeatureFlag() { @@ -120,6 +143,15 @@ FeatureFlag DynamicPartitionControlAndroid::GetVirtualAbFeatureFlag() { return virtual_ab_; } +FeatureFlag +DynamicPartitionControlAndroid::GetVirtualAbCompressionFeatureFlag() { + if constexpr (constants::kIsRecovery) { + // Don't attempt VABC in recovery + return FeatureFlag(FeatureFlag::Value::NONE); + } + return virtual_ab_compression_; +} + bool DynamicPartitionControlAndroid::OptimizeOperation( const std::string& partition_name, const InstallOperation& operation, @@ -253,9 +285,10 @@ bool DynamicPartitionControlAndroid::UnmapPartitionOnDeviceMapper( return true; } -void DynamicPartitionControlAndroid::UnmapAllPartitions() { +bool DynamicPartitionControlAndroid::UnmapAllPartitions() { + snapshot_->UnmapAllSnapshots(); if (mapped_devices_.empty()) { - return; + return false; } // UnmapPartitionOnDeviceMapper removes objects from mapped_devices_, hence // a copy is needed for the loop. @@ -264,6 +297,7 @@ void DynamicPartitionControlAndroid::UnmapAllPartitions() { for (const auto& partition_name : mapped) { ignore_result(UnmapPartitionOnDeviceMapper(partition_name)); } + return true; } void DynamicPartitionControlAndroid::Cleanup() { @@ -287,9 +321,16 @@ bool DynamicPartitionControlAndroid::GetDmDevicePathByName( std::unique_ptr<MetadataBuilder> DynamicPartitionControlAndroid::LoadMetadataBuilder( - const std::string& super_device, uint32_t source_slot) { - return LoadMetadataBuilder( - super_device, source_slot, BootControlInterface::kInvalidSlot); + 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> @@ -297,26 +338,19 @@ 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) { - builder = - MetadataBuilder::New(PartitionOpener(), super_device, source_slot); - } else { - bool always_keep_source_slot = !target_supports_snapshot_; - builder = MetadataBuilder::NewForUpdate(PartitionOpener(), - super_device, - source_slot, - target_slot, - always_keep_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; @@ -428,17 +462,17 @@ bool DynamicPartitionControlAndroid::PreparePartitionsForUpdate( 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. - is_target_dynamic_ = !manifest.dynamic_partition_metadata().groups().empty(); if (!is_target_dynamic_) { return true; } - target_supports_snapshot_ = - manifest.dynamic_partition_metadata().snapshot_enabled(); - if (!update) return true; @@ -489,8 +523,60 @@ bool DynamicPartitionControlAndroid::PreparePartitionsForUpdate( } } - return PrepareDynamicPartitionsForUpdate( - source_slot, target_slot, manifest, delete_source); + // 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 { @@ -721,9 +807,7 @@ bool DynamicPartitionControlAndroid::PrepareDynamicPartitionsForUpdate( } std::string device_dir_str; - if (!GetDeviceDir(&device_dir_str)) { - return false; - } + TEST_AND_RETURN_FALSE(GetDeviceDir(&device_dir_str)); base::FilePath device_dir(device_dir_str); auto source_device = device_dir.Append(GetSuperPartitionName(source_slot)).value(); @@ -740,21 +824,125 @@ bool DynamicPartitionControlAndroid::PrepareDynamicPartitionsForUpdate( DeleteSourcePartitions(builder.get(), source_slot, manifest)); } - if (!UpdatePartitionMetadata(builder.get(), target_slot, manifest)) { - return false; - } + TEST_AND_RETURN_FALSE( + UpdatePartitionMetadata(builder.get(), target_slot, manifest)); auto target_device = device_dir.Append(GetSuperPartitionName(target_slot)).value(); return StoreMetadata(target_device, builder.get(), target_slot); } +DynamicPartitionControlAndroid::SpaceLimit +DynamicPartitionControlAndroid::GetSpaceLimit(bool use_snapshot) { + // On device retrofitting dynamic partitions, allocatable_space = "super", + // where "super" is the sum of all block devices for that slot. Since block + // devices are dedicated for the corresponding slot, there's no need to halve + // the allocatable space. + if (GetDynamicPartitionsFeatureFlag().IsRetrofit()) + return SpaceLimit::ERROR_IF_EXCEEDED_SUPER; + + // On device launching dynamic partitions w/o VAB, regardless of recovery + // sideload, super partition must be big enough to hold both A and B slots of + // groups. Hence, + // allocatable_space = super / 2 + if (!GetVirtualAbFeatureFlag().IsEnabled()) + return SpaceLimit::ERROR_IF_EXCEEDED_HALF_OF_SUPER; + + // Source build supports VAB. Super partition must be big enough to hold + // one slot of groups (ERROR_IF_EXCEEDED_SUPER). However, there are cases + // where additional warning messages needs to be written. + + // If using snapshot updates, implying that target build also uses VAB, + // allocatable_space = super + if (use_snapshot) + return SpaceLimit::ERROR_IF_EXCEEDED_SUPER; + + // Source build supports VAB but not using snapshot updates. There are + // several cases, as listed below. + // Sideloading: allocatable_space = super. + if (IsRecovery()) + return SpaceLimit::ERROR_IF_EXCEEDED_SUPER; + + // On launch VAB device, this implies secondary payload. + // Technically, we don't have to check anything, but sum(groups) < super + // still applies. + if (!GetVirtualAbFeatureFlag().IsRetrofit()) + return SpaceLimit::ERROR_IF_EXCEEDED_SUPER; + + // On retrofit VAB device, either of the following: + // - downgrading: allocatable_space = super / 2 + // - secondary payload: don't check anything + // These two cases are indistinguishable, + // hence emit warning if sum(groups) > super / 2 + return SpaceLimit::WARN_IF_EXCEEDED_HALF_OF_SUPER; +} + +bool DynamicPartitionControlAndroid::CheckSuperPartitionAllocatableSpace( + android::fs_mgr::MetadataBuilder* builder, + const DeltaArchiveManifest& manifest, + bool use_snapshot) { + uint64_t sum_groups = 0; + for (const auto& group : manifest.dynamic_partition_metadata().groups()) { + sum_groups += group.size(); + } + + uint64_t full_space = builder->AllocatableSpace(); + uint64_t half_space = full_space / 2; + constexpr const char* fmt = + "The maximum size of all groups for the target slot (%" PRIu64 + ") has exceeded %sallocatable space for dynamic partitions %" PRIu64 "."; + switch (GetSpaceLimit(use_snapshot)) { + case SpaceLimit::ERROR_IF_EXCEEDED_HALF_OF_SUPER: { + if (sum_groups > half_space) { + LOG(ERROR) << StringPrintf(fmt, sum_groups, "HALF OF ", half_space); + return false; + } + // If test passes, it implies that the following two conditions also pass. + break; + } + case SpaceLimit::WARN_IF_EXCEEDED_HALF_OF_SUPER: { + if (sum_groups > half_space) { + LOG(WARNING) << StringPrintf(fmt, sum_groups, "HALF OF ", half_space) + << " This is allowed for downgrade or secondary OTA on " + "retrofit VAB device."; + } + // still check sum(groups) < super + [[fallthrough]]; + } + case SpaceLimit::ERROR_IF_EXCEEDED_SUPER: { + if (sum_groups > full_space) { + LOG(ERROR) << base::StringPrintf(fmt, sum_groups, "", full_space); + return false; + } + break; + } + } + + return true; +} + bool DynamicPartitionControlAndroid::PrepareSnapshotPartitionsForUpdate( uint32_t source_slot, uint32_t target_slot, const DeltaArchiveManifest& manifest, uint64_t* required_size) { TEST_AND_RETURN_FALSE(ExpectMetadataMounted()); + + 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(source_slot)).value(); + auto builder = LoadMetadataBuilder(super_device, source_slot); + if (builder == nullptr) { + LOG(ERROR) << "No metadata at " + << BootControlInterface::SlotName(source_slot); + return false; + } + + TEST_AND_RETURN_FALSE( + CheckSuperPartitionAllocatableSpace(builder.get(), manifest, true)); + if (!snapshot_->BeginUpdate()) { LOG(ERROR) << "Cannot begin new update."; return false; @@ -780,6 +968,18 @@ bool DynamicPartitionControlAndroid::UpdatePartitionMetadata( MetadataBuilder* builder, uint32_t target_slot, const DeltaArchiveManifest& manifest) { + // Check preconditions. + if (GetVirtualAbFeatureFlag().IsEnabled()) { + CHECK(!target_supports_snapshot_ || IsRecovery()) + << "Must use snapshot on VAB device when target build supports VAB and " + "not sideloading."; + LOG_IF(INFO, !target_supports_snapshot_) + << "Not using snapshot on VAB device because target build does not " + "support snapshot. Secondary or downgrade OTA?"; + LOG_IF(INFO, IsRecovery()) + << "Not using snapshot on VAB device because sideloading."; + } + // If applying downgrade from Virtual A/B to non-Virtual A/B, the left-over // COW group needs to be deleted to ensure there are enough space to create // target partitions. @@ -788,29 +988,8 @@ bool DynamicPartitionControlAndroid::UpdatePartitionMetadata( 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; - } + TEST_AND_RETURN_FALSE( + CheckSuperPartitionAllocatableSpace(builder, manifest, false)); // name of partition(e.g. "system") -> size in bytes std::map<std::string, uint64_t> partition_sizes; @@ -854,6 +1033,12 @@ bool DynamicPartitionControlAndroid::UpdatePartitionMetadata( << " to size " << partition_size << ". Not enough space?"; return false; } + if (p->size() < partition_size) { + LOG(ERROR) << "Partition " << partition_name_suffix + << " was expected to have size " << partition_size + << ", but instead has size " << p->size(); + return false; + } LOG(INFO) << "Added partition " << partition_name_suffix << " to group " << group_name_suffix << " with size " << partition_size; } @@ -879,38 +1064,99 @@ 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) { + auto partition_dev = + GetPartitionDevice(partition_name, slot, current_slot, not_in_payload); + if (!partition_dev.has_value()) { + return false; + } + if (device) { + *device = std::move(partition_dev->rw_device_path); + } + if (is_dynamic) { + *is_dynamic = partition_dev->is_dynamic; + } + 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); +} + +static std::string GetStaticDevicePath( + const base::FilePath& device_dir, + const std::string& partition_name_suffixed) { + base::FilePath path = device_dir.Append(partition_name_suffixed); + return path.value(); +} + +std::optional<PartitionDevice> +DynamicPartitionControlAndroid::GetPartitionDevice( + const std::string& partition_name, + uint32_t slot, + uint32_t current_slot, + bool not_in_payload) { + std::string device_dir_str; + if (!GetDeviceDir(&device_dir_str)) { + LOG(ERROR) << "Failed to GetDeviceDir()"; + return {}; + } + const base::FilePath device_dir(device_dir_str); + // When VABC is enabled, we can't get device path for dynamic partitions in + // target slot. 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 (UpdateUsesSnapshotCompression() && slot != current_slot && + IsDynamicPartition(partition_name, slot)) { + return { + {.readonly_device_path = base::FilePath{std::string{VABC_DEVICE_DIR}} + .Append(partition_name_suffix) + .value(), + .is_dynamic = true}}; + } // 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. + std::string device; if (GetDynamicPartitionsFeatureFlag().IsEnabled() && (slot == current_slot || is_target_dynamic_)) { - switch (GetDynamicPartitionDevice( - device_dir, partition_name_suffix, slot, current_slot, device)) { + switch (GetDynamicPartitionDevice(device_dir, + partition_name_suffix, + slot, + current_slot, + not_in_payload, + &device)) { case DynamicPartitionDeviceStatus::SUCCESS: - return true; + return {{.rw_device_path = device, + .readonly_device_path = device, + .is_dynamic = true}}; + case DynamicPartitionDeviceStatus::TRY_STATIC: break; case DynamicPartitionDeviceStatus::ERROR: // fallthrough default: - return false; + return {}; } } - base::FilePath path = device_dir.Append(partition_name_suffix); - if (!DeviceExists(path.value())) { - LOG(ERROR) << "Device file " << path.value() << " does not exist."; - return false; + // Try static partitions. + auto static_path = GetStaticDevicePath(device_dir, partition_name_suffix); + if (!DeviceExists(static_path)) { + LOG(ERROR) << "Device file " << static_path << " does not exist."; + return {}; } - *device = path.value(); - return true; + return {{.rw_device_path = static_path, + .readonly_device_path = static_path, + .is_dynamic = false}}; } bool DynamicPartitionControlAndroid::IsSuperBlockDevice( @@ -929,6 +1175,7 @@ DynamicPartitionControlAndroid::GetDynamicPartitionDevice( 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(); @@ -968,7 +1215,7 @@ DynamicPartitionControlAndroid::GetDynamicPartitionDevice( } } - bool force_writable = slot != current_slot; + bool force_writable = (slot != current_slot) && !not_in_payload; if (MapPartitionOnDeviceMapper( super_device, partition_name_suffix, slot, force_writable, device)) { return DynamicPartitionDeviceStatus::SUCCESS; @@ -982,7 +1229,7 @@ void DynamicPartitionControlAndroid::set_fake_mapped_devices( } bool DynamicPartitionControlAndroid::IsRecovery() { - return kIsRecovery; + return constants::kIsRecovery; } static bool IsIncrementalUpdate(const DeltaArchiveManifest& manifest) { @@ -1010,7 +1257,7 @@ bool DynamicPartitionControlAndroid::DeleteSourcePartitions( LOG(INFO) << "Will overwrite existing partitions. Slot " << BootControlInterface::SlotName(source_slot) - << "may be unbootable until update finishes!"; + << " may be unbootable until update finishes!"; const std::string source_suffix = SlotSuffixForSlotNumber(source_slot); DeleteGroupsWithSuffix(builder, source_suffix); @@ -1054,6 +1301,76 @@ bool DynamicPartitionControlAndroid::ResetUpdate(PrefsInterface* prefs) { return true; } +bool DynamicPartitionControlAndroid::ListDynamicPartitionsForSlot( + uint32_t slot, + uint32_t current_slot, + std::vector<std::string>* partitions) { + CHECK(slot == source_slot_ || target_slot_ != UINT32_MAX) + << " source slot: " << source_slot_ << " target slot: " << target_slot_ + << " slot: " << slot + << " attempting to query dynamic partition metadata for target slot " + "before PreparePartitionForUpdate() is called. The " + "metadata in target slot isn't valid until " + "PreparePartitionForUpdate() is called, contining execution would " + "likely cause problems."; + bool slot_enables_dynamic_partitions = + GetDynamicPartitionsFeatureFlag().IsEnabled(); + // Check if the target slot has dynamic partitions, this may happen when + // applying a retrofit package. + if (slot != current_slot) { + slot_enables_dynamic_partitions = + slot_enables_dynamic_partitions && is_target_dynamic_; + } + + if (!slot_enables_dynamic_partitions) { + LOG(INFO) << "Dynamic partition is not enabled for slot " << slot; + return true; + } + + 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(slot)).value(); + auto builder = LoadMetadataBuilder(super_device, slot); + TEST_AND_RETURN_FALSE(builder != nullptr); + + std::vector<std::string> result; + auto suffix = SlotSuffixForSlotNumber(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()) { @@ -1081,4 +1398,80 @@ bool DynamicPartitionControlAndroid::EnsureMetadataMounted() { return metadata_device_ != nullptr; } +std::unique_ptr<android::snapshot::ISnapshotWriter> +DynamicPartitionControlAndroid::OpenCowWriter( + const std::string& partition_name, + const std::optional<std::string>& source_path, + bool is_append) { + auto suffix = SlotSuffixForSlotNumber(target_slot_); + + auto super_device = GetSuperDevice(); + if (!super_device.has_value()) { + return nullptr; + } + CreateLogicalPartitionParams params = { + .block_device = super_device->value(), + .metadata_slot = target_slot_, + .partition_name = partition_name + suffix, + .force_writable = true, + .timeout_ms = kMapSnapshotTimeout}; + // TODO(zhangkelvin) Open an APPEND mode CowWriter once there's an API to do + // it. + return snapshot_->OpenSnapshotWriter(params, std::move(source_path)); +} // namespace chromeos_update_engine + +FileDescriptorPtr DynamicPartitionControlAndroid::OpenCowFd( + const std::string& unsuffixed_partition_name, + const std::optional<std::string>& source_path, + bool is_append) { + auto cow_writer = + OpenCowWriter(unsuffixed_partition_name, source_path, is_append); + if (cow_writer == nullptr) { + return nullptr; + } + if (!cow_writer->InitializeAppend(kEndOfInstallLabel)) { + return nullptr; + } + return std::make_shared<CowWriterFileDescriptor>(std::move(cow_writer)); +} + +std::optional<base::FilePath> DynamicPartitionControlAndroid::GetSuperDevice() { + std::string device_dir_str; + if (!GetDeviceDir(&device_dir_str)) { + LOG(ERROR) << "Failed to get device dir!"; + return {}; + } + base::FilePath device_dir(device_dir_str); + auto super_device = device_dir.Append(GetSuperPartitionName(target_slot_)); + return super_device; +} + +bool DynamicPartitionControlAndroid::MapAllPartitions() { + return snapshot_->MapAllSnapshots(kMapSnapshotTimeout); +} + +bool DynamicPartitionControlAndroid::IsDynamicPartition( + const std::string& partition_name, uint32_t slot) { + if (slot >= dynamic_partition_list_.size()) { + LOG(ERROR) << "Seeing unexpected slot # " << slot << " currently assuming " + << dynamic_partition_list_.size() << " slots"; + return false; + } + auto& dynamic_partition_list = dynamic_partition_list_[slot]; + if (dynamic_partition_list.empty() && + GetDynamicPartitionsFeatureFlag().IsEnabled()) { + // Use the DAP config of the target slot. + CHECK(ListDynamicPartitionsForSlot( + slot, source_slot_, &dynamic_partition_list)); + } + return std::find(dynamic_partition_list.begin(), + dynamic_partition_list.end(), + partition_name) != dynamic_partition_list.end(); +} + +bool DynamicPartitionControlAndroid::UpdateUsesSnapshotCompression() { + return GetVirtualAbFeatureFlag().IsEnabled() && + snapshot_->UpdateUsesCompression(); +} + } // namespace chromeos_update_engine diff --git a/dynamic_partition_control_android.h b/aosp/dynamic_partition_control_android.h index 8ad75933..df914018 100644 --- a/dynamic_partition_control_android.h +++ b/aosp/dynamic_partition_control_android.h @@ -14,16 +14,19 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_ANDROID_H_ -#define UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_ANDROID_H_ +#ifndef UPDATE_ENGINE_AOSP_DYNAMIC_PARTITION_CONTROL_ANDROID_H_ +#define UPDATE_ENGINE_AOSP_DYNAMIC_PARTITION_CONTROL_ANDROID_H_ #include <memory> #include <set> #include <string> +#include <string_view> +#include <vector> #include <base/files/file_util.h> #include <libsnapshot/auto_device.h> #include <libsnapshot/snapshot.h> +#include <libsnapshot/snapshot_writer.h> #include "update_engine/common/dynamic_partition_control_interface.h" @@ -31,10 +34,16 @@ namespace chromeos_update_engine { class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface { public: - DynamicPartitionControlAndroid(); + // A directory where all partitions mapped by VABC is expected to be found. + // Per earlier discussion with VAB team, this directory is unlikely to change. + // So we declare it as a constant here. + static constexpr std::string_view VABC_DEVICE_DIR = "/dev/block/mapper/"; + explicit DynamicPartitionControlAndroid(uint32_t source_slot); ~DynamicPartitionControlAndroid(); + FeatureFlag GetDynamicPartitionsFeatureFlag() override; FeatureFlag GetVirtualAbFeatureFlag() override; + FeatureFlag GetVirtualAbCompressionFeatureFlag() override; bool OptimizeOperation(const std::string& partition_name, const InstallOperation& operation, InstallOperation* optimized) override; @@ -53,16 +62,58 @@ class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface { bool ResetUpdate(PrefsInterface* prefs) override; + bool ListDynamicPartitionsForSlot( + uint32_t slot, + uint32_t current_slot, + std::vector<std::string>* partitions) override; + + bool VerifyExtentsForUntouchedPartitions( + uint32_t source_slot, + 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(). + std::optional<PartitionDevice> GetPartitionDevice( + const std::string& partition_name, + uint32_t slot, + uint32_t current_slot, + bool not_in_payload); + // Deprecated, please use GetPartitionDevice(string, uint32_t, uint32_t); + // TODO(zhangkelvin) Remove below deprecated APIs. + 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); + // Partition name is expected to be unsuffixed. e.g. system, vendor + // Return an interface to write to a snapshoted partition. + std::unique_ptr<android::snapshot::ISnapshotWriter> OpenCowWriter( + const std::string& unsuffixed_partition_name, + const std::optional<std::string>& source_path, + bool is_append) override; + FileDescriptorPtr OpenCowFd(const std::string& unsuffixed_partition_name, + const std::optional<std::string>&, + bool is_append = false) override; + + bool UnmapAllPartitions() override; + + bool IsDynamicPartition(const std::string& part_name, uint32_t slot) override; + + bool UpdateUsesSnapshotCompression() override; + protected: // These functions are exposed for testing. @@ -72,16 +123,14 @@ class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface { virtual bool UnmapPartitionOnDeviceMapper( const std::string& target_partition_name); - // Retrieve metadata from |super_device| at slot |source_slot|. - // - // If |target_slot| != kInvalidSlot, before returning the metadata, this - // function modifies the metadata so that during updates, the metadata can be - // written to |target_slot|. In particular, on retrofit devices, the returned + // 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|. - // - // If |target_slot| == kInvalidSlot, this function returns metadata at - // |source_slot| without modifying it. This is the same as - // LoadMetadataBuilder(const std::string&, uint32_t). virtual std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder( const std::string& super_device, uint32_t source_slot, @@ -120,13 +169,6 @@ class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface { virtual bool GetDmDevicePathByName(const std::string& name, std::string* path); - // Retrieve metadata from |super_device| at slot |source_slot|. - virtual std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder( - const std::string& super_device, uint32_t source_slot); - - // Return a possible location for devices listed by name. - virtual bool GetDeviceDir(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); @@ -173,38 +215,68 @@ class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface { 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); + + bool MapAllPartitions() override; + + void SetSourceSlot(uint32_t slot) { source_slot_ = slot; } + void SetTargetSlot(uint32_t slot) { target_slot_ = slot; } + private: friend class DynamicPartitionControlAndroidTest; + friend class SnapshotPartitionTestP; + + std::optional<base::FilePath> GetSuperDevice(); - 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|, assuming the device - // does not have Virtual A/B. + // 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 devices with dynamic - // partitions updating without snapshots. - // If |delete_source| is set, source partitions are deleted before resizing - // target partitions (using DeleteSourcePartitions). - bool PrepareDynamicPartitionsForUpdate(uint32_t source_slot, - uint32_t target_slot, - const DeltaArchiveManifest& manifest, - bool delete_source); - - // Helper for PreparePartitionsForUpdate. Used for snapshotted partitions for - // Virtual A/B update. + // 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 SpaceLimit { + // Most restricted: if sum(groups) > super / 2, error + ERROR_IF_EXCEEDED_HALF_OF_SUPER, + // Implies ERROR_IF_EXCEEDED_SUPER; then, if sum(groups) > super / 2, warn + WARN_IF_EXCEEDED_HALF_OF_SUPER, + // Least restricted: if sum(groups) > super, error + ERROR_IF_EXCEEDED_SUPER, + }; + // Helper of CheckSuperPartitionAllocatableSpace. Determine limit for groups + // and partitions. + SpaceLimit GetSpaceLimit(bool use_snapshot); + + // Returns true if the allocatable space in super partition is larger than + // the size of dynamic partition groups in the manifest. + bool CheckSuperPartitionAllocatableSpace( + android::fs_mgr::MetadataBuilder* builder, + const DeltaArchiveManifest& manifest, + bool use_snapshot); + enum class DynamicPartitionDeviceStatus { SUCCESS, ERROR, @@ -220,6 +292,7 @@ class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface { 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 @@ -236,17 +309,20 @@ class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface { // 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. + // 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 + // - 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. + // - 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. + // 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. @@ -255,21 +331,28 @@ class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface { // 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::SnapshotManager> snapshot_; + const FeatureFlag virtual_ab_compression_; + 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; + std::vector<std::vector<std::string>> dynamic_partition_list_{2UL}; DISALLOW_COPY_AND_ASSIGN(DynamicPartitionControlAndroid); }; } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_ANDROID_H_ +#endif // UPDATE_ENGINE_AOSP_DYNAMIC_PARTITION_CONTROL_ANDROID_H_ diff --git a/dynamic_partition_control_android_unittest.cc b/aosp/dynamic_partition_control_android_unittest.cc index 20819182..6f1d4ef8 100644 --- a/dynamic_partition_control_android_unittest.cc +++ b/aosp/dynamic_partition_control_android_unittest.cc @@ -14,8 +14,9 @@ // limitations under the License. // -#include "update_engine/dynamic_partition_control_android.h" +#include "update_engine/aosp/dynamic_partition_control_android.h" +#include <algorithm> #include <set> #include <vector> @@ -24,19 +25,21 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> #include <libavb/libavb.h> +#include <libsnapshot/mock_snapshot.h> +#include "update_engine/aosp/dynamic_partition_test_utils.h" +#include "update_engine/aosp/mock_dynamic_partition_control_android.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::AtLeast; using testing::Invoke; using testing::NiceMock; using testing::Not; @@ -54,7 +57,10 @@ class DynamicPartitionControlAndroidTest : public ::testing::Test { .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); ON_CALL(dynamicControl(), GetVirtualAbFeatureFlag()) .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::NONE))); - + ON_CALL(dynamicControl(), GetVirtualAbCompressionFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::NONE))); + ON_CALL(dynamicControl(), UpdateUsesSnapshotCompression()) + .WillByDefault(Return(false)); ON_CALL(dynamicControl(), GetDeviceDir(_)) .WillByDefault(Invoke([](auto path) { *path = kFakeDevicePath; @@ -72,6 +78,17 @@ class DynamicPartitionControlAndroidTest : public ::testing::Test { 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. @@ -100,13 +117,24 @@ class DynamicPartitionControlAndroidTest : public ::testing::Test { // |slot|. void SetMetadata(uint32_t slot, const PartitionSuffixSizes& sizes, - uint32_t partition_attr = 0) { + 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([sizes, partition_attr](auto, auto, auto) { + .WillRepeatedly(Invoke([=](auto, auto, auto) { return NewFakeMetadata(PartitionSuffixSizesToManifest(sizes), - partition_attr); + partition_attr, + super_size); })); } @@ -194,6 +222,8 @@ class DynamicPartitionControlAndroidTestP void SetUp() override { DynamicPartitionControlAndroidTest::SetUp(); SetSlots(GetParam()); + dynamicControl().SetSourceSlot(source()); + dynamicControl().SetTargetSlot(target()); } }; @@ -363,6 +393,92 @@ TEST_P(DynamicPartitionControlAndroidTestP, EXPECT_EQ(GetDevice(T("bar")), bar_device); } +TEST_P(DynamicPartitionControlAndroidTestP, GetMountableDevicePath) { + ON_CALL(dynamicControl(), GetDynamicPartitionsFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); + ON_CALL(dynamicControl(), GetVirtualAbFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); + ON_CALL(dynamicControl(), GetVirtualAbCompressionFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::NONE))); + ON_CALL(dynamicControl(), UpdateUsesSnapshotCompression()) + .WillByDefault(Return(false)); + ON_CALL(dynamicControl(), IsDynamicPartition(_, _)) + .WillByDefault(Return(true)); + + EXPECT_CALL(dynamicControl(), + DeviceExists(AnyOf(GetDevice(S("vendor")), + GetDevice(T("vendor")), + GetDevice(S("system")), + GetDevice(T("system"))))) + .WillRepeatedly(Return(true)); + EXPECT_CALL( + dynamicControl(), + GetState(AnyOf(S("vendor"), T("vendor"), S("system"), T("system")))) + .WillRepeatedly(Return(DmDeviceState::ACTIVE)); + + SetMetadata(source(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}}); + SetMetadata(target(), {{T("system"), 2_GiB}, {T("vendor"), 1_GiB}}); + std::string device; + ASSERT_TRUE(dynamicControl().GetPartitionDevice( + "system", source(), source(), &device)); + ASSERT_EQ(GetDmDevice(S("system")), device); + + ASSERT_TRUE(dynamicControl().GetPartitionDevice( + "system", target(), source(), &device)); + ASSERT_EQ(GetDevice(T("system")), device); + + // If VABC is disabled, mountable device path should be same as device path. + auto device_info = + dynamicControl().GetPartitionDevice("system", target(), source(), false); + ASSERT_TRUE(device_info.has_value()); + ASSERT_EQ(device_info->readonly_device_path, device); +} + +TEST_P(DynamicPartitionControlAndroidTestP, GetMountableDevicePathVABC) { + ON_CALL(dynamicControl(), GetDynamicPartitionsFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); + ON_CALL(dynamicControl(), GetVirtualAbFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); + ON_CALL(dynamicControl(), GetVirtualAbCompressionFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); + ON_CALL(dynamicControl(), UpdateUsesSnapshotCompression()) + .WillByDefault(Return(true)); + EXPECT_CALL(dynamicControl(), IsDynamicPartition(_, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + + EXPECT_CALL(dynamicControl(), + DeviceExists(AnyOf(GetDevice(S("vendor")), + GetDevice(T("vendor")), + GetDevice(S("system")), + GetDevice(T("system"))))) + .WillRepeatedly(Return(true)); + EXPECT_CALL( + dynamicControl(), + GetState(AnyOf(S("vendor"), T("vendor"), S("system"), T("system")))) + .WillRepeatedly(Return(DmDeviceState::ACTIVE)); + + SetMetadata(source(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}}); + SetMetadata(target(), {{T("system"), 2_GiB}, {T("vendor"), 1_GiB}}); + + std::string device; + ASSERT_TRUE(dynamicControl().GetPartitionDevice( + "system", source(), source(), &device)); + ASSERT_EQ(GetDmDevice(S("system")), device); + + ASSERT_TRUE(dynamicControl().GetPartitionDevice( + "system", target(), source(), &device)); + ASSERT_EQ("", device); + + auto device_info = + dynamicControl().GetPartitionDevice("system", target(), source(), false); + ASSERT_TRUE(device_info.has_value()); + base::FilePath vabc_device_dir{ + std::string{DynamicPartitionControlAndroid::VABC_DEVICE_DIR}}; + ASSERT_EQ(device_info->readonly_device_path, + vabc_device_dir.Append(T("system")).value()); +} + TEST_P(DynamicPartitionControlAndroidTestP, GetPartitionDeviceWhenResumingUpdate) { // Static partition bar_{a,b} exists. @@ -769,11 +885,11 @@ TEST_F(DynamicPartitionControlAndroidTest, ResetUpdate) { } 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 + "system /postinstall ext4 ro,nosuid,nodev,noexec " + "slotselect_other,logical\n" + "/dev/block/by-name/system /postinstall ext4 " + "ro,nosuid,nodev,noexec slotselect_other\n"; ScopedTempFile fstab; ASSERT_TRUE(test_utils::WriteFileString(fstab.path(), fstab_content)); ASSERT_THAT(dynamicControl().RealIsAvbEnabledInFstab(fstab.path()), @@ -781,10 +897,9 @@ TEST_F(DynamicPartitionControlAndroidTest, IsAvbNotEnabledInFstab) { } 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 + "system /postinstall ext4 ro,nosuid,nodev,noexec " + "slotselect_other,logical,avb_keys=/foo\n"; ScopedTempFile fstab; ASSERT_TRUE(test_utils::WriteFileString(fstab.path(), fstab_content)); ASSERT_THAT(dynamicControl().RealIsAvbEnabledInFstab(fstab.path()), @@ -892,4 +1007,123 @@ TEST_P(DynamicPartitionControlAndroidTestP, EraseSystemOtherAvbFooter) { 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()); + SetMetadata(source(), {}); + 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; + + SetMetadata(source(), {}); + 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)); + + // Metadata is needed to perform super partition size check. + SetMetadata(source(), {}); + + // 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_test_utils.h b/aosp/dynamic_partition_test_utils.h index 70a176b5..c5183823 100644 --- a/dynamic_partition_test_utils.h +++ b/aosp/dynamic_partition_test_utils.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_DYNAMIC_PARTITION_TEST_UTILS_H_ -#define UPDATE_ENGINE_DYNAMIC_PARTITION_TEST_UTILS_H_ +#ifndef UPDATE_ENGINE_AOSP_DYNAMIC_PARTITION_TEST_UTILS_H_ +#define UPDATE_ENGINE_AOSP_DYNAMIC_PARTITION_TEST_UTILS_H_ #include <stdint.h> @@ -47,7 +47,7 @@ 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 std::string_view kFakeDevicePath = "/fake/dev/path/"; constexpr const char* kFakeDmDevicePath = "/fake/dm/dev/path/"; constexpr const uint32_t kFakeMetadataSize = 65536; constexpr const char* kDefaultGroup = "foo"; @@ -112,7 +112,7 @@ inline std::ostream& operator<<(std::ostream& os, } inline std::string GetDevice(const std::string& name) { - return kFakeDevicePath + name; + return std::string(kFakeDevicePath) + name; } inline std::string GetDmDevice(const std::string& name) { @@ -175,9 +175,11 @@ inline DeltaArchiveManifest PartitionSizesToManifest( } inline std::unique_ptr<MetadataBuilder> NewFakeMetadata( - const DeltaArchiveManifest& manifest, uint32_t partition_attr = 0) { + const DeltaArchiveManifest& manifest, + uint32_t partition_attr = 0, + uint64_t super_size = kDefaultSuperSize) { auto builder = - MetadataBuilder::New(kDefaultSuperSize, kFakeMetadataSize, kMaxNumSlots); + 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()) { @@ -283,4 +285,4 @@ inline std::ostream& operator<<(std::ostream& os, const TestParam& param) { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_DYNAMIC_PARTITION_TEST_UTILS_H_ +#endif // UPDATE_ENGINE_AOSP_DYNAMIC_PARTITION_TEST_UTILS_H_ diff --git a/dynamic_partition_utils.cc b/aosp/dynamic_partition_utils.cc index f9bd886b..6b77a45c 100644 --- a/dynamic_partition_utils.cc +++ b/aosp/dynamic_partition_utils.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/dynamic_partition_utils.h" +#include "update_engine/aosp/dynamic_partition_utils.h" #include <vector> diff --git a/dynamic_partition_utils.h b/aosp/dynamic_partition_utils.h index 09fce00c..5a51d5e1 100644 --- a/dynamic_partition_utils.h +++ b/aosp/dynamic_partition_utils.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_DYNAMIC_PARTITION_UTILS_H_ -#define UPDATE_ENGINE_DYNAMIC_PARTITION_UTILS_H_ +#ifndef UPDATE_ENGINE_AOSP_DYNAMIC_PARTITION_UTILS_H_ +#define UPDATE_ENGINE_AOSP_DYNAMIC_PARTITION_UTILS_H_ #include <string> @@ -30,4 +30,4 @@ void DeleteGroupsWithSuffix(android::fs_mgr::MetadataBuilder* builder, } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_DYNAMIC_PARTITION_UTILS_H_ +#endif // UPDATE_ENGINE_AOSP_DYNAMIC_PARTITION_UTILS_H_ diff --git a/hardware_android.cc b/aosp/hardware_android.cc index 068468b8..624cfc99 100644 --- a/hardware_android.cc +++ b/aosp/hardware_android.cc @@ -14,18 +14,32 @@ // limitations under the License. // -#include "update_engine/hardware_android.h" +#include "update_engine/aosp/hardware_android.h" #include <sys/types.h> #include <memory> +#include <string> +#include <string_view> +#include <android/sysprop/GkiProperties.sysprop.h> #include <android-base/properties.h> #include <base/files/file_util.h> +#include <base/strings/string_number_conversions.h> +#include <base/strings/string_util.h> #include <bootloader_message/bootloader_message.h> +#include <fstab/fstab.h> +#include <libavb/libavb.h> +#include <libavb_user/avb_ops_user.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" + +#ifndef __ANDROID_RECOVERY__ +#include <android/sysprop/OtaProperties.sysprop.h> +#endif using android::base::GetBoolProperty; using android::base::GetIntProperty; @@ -39,13 +53,63 @@ namespace { // Android properties that identify the hardware and potentially non-updatable // parts of the bootloader (such as the bootloader version and the baseband // version). -const char kPropBootBootloader[] = "ro.boot.bootloader"; -const char kPropBootBaseband[] = "ro.boot.baseband"; const char kPropProductManufacturer[] = "ro.product.manufacturer"; 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", + ""); +} + +ErrorCode IsTimestampNewerLogged(const std::string& partition_name, + const std::string& old_version, + const std::string& new_version) { + auto error_code = utils::IsTimestampNewer(old_version, new_version); + if (error_code != ErrorCode::kSuccess) { + LOG(WARNING) << "Timestamp check failed with " + << utils::ErrorCodeToString(error_code) << ": " + << partition_name << " Partition timestamp: " << old_version + << " Update timestamp: " << new_version; + } + return error_code; +} + +void SetVbmetaDigestProp(const std::string& value) { +#ifndef __ANDROID_RECOVERY__ + if (!android::sysprop::OtaProperties::other_vbmeta_digest(value)) { + LOG(WARNING) << "Failed to set other vbmeta digest to " << value; + } +#endif +} + +std::string CalculateVbmetaDigestForInactiveSlot() { + AvbSlotVerifyData* avb_slot_data; + + auto suffix = fs_mgr_get_other_slot_suffix(); + const char* requested_partitions[] = {nullptr}; + auto avb_ops = avb_ops_user_new(); + auto verify_result = avb_slot_verify(avb_ops, + requested_partitions, + suffix.c_str(), + AVB_SLOT_VERIFY_FLAGS_NONE, + AVB_HASHTREE_ERROR_MODE_EIO, + &avb_slot_data); + if (verify_result != AVB_SLOT_VERIFY_RESULT_OK) { + LOG(WARNING) << "Failed to verify avb slot data: " << verify_result; + return ""; + } + + uint8_t vbmeta_digest[AVB_SHA256_DIGEST_SIZE]; + avb_slot_verify_data_calculate_vbmeta_digest( + avb_slot_data, AVB_DIGEST_TYPE_SHA256, vbmeta_digest); + + std::string encoded_digest = + base::HexEncode(vbmeta_digest, AVB_SHA256_DIGEST_SIZE); + return base::ToLowerASCII(encoded_digest); +} + } // namespace namespace hardware { @@ -113,12 +177,9 @@ string HardwareAndroid::GetHardwareClass() const { return manufacturer + ":" + sku + ":" + revision; } -string HardwareAndroid::GetFirmwareVersion() const { - return GetProperty(kPropBootBootloader, ""); -} - -string HardwareAndroid::GetECVersion() const { - return GetProperty(kPropBootBaseband, ""); +string HardwareAndroid::GetDeviceRequisition() const { + LOG(WARNING) << "STUB: Getting requisition is not supported."; + return ""; } int HardwareAndroid::GetMinKernelKeyVersion() const { @@ -152,9 +213,10 @@ int HardwareAndroid::GetPowerwashCount() const { return 0; } -bool HardwareAndroid::SchedulePowerwash(bool is_rollback) { +bool HardwareAndroid::SchedulePowerwash(bool save_rollback_data) { LOG(INFO) << "Scheduling a powerwash to BCB."; - LOG_IF(WARNING, is_rollback) << "is_rollback was true but isn't supported."; + LOG_IF(WARNING, save_rollback_data) << "save_rollback_data was true but " + << "isn't supported."; string err; if (!update_bootloader_message({"--wipe_data", "--reason=wipe_data_from_ota"}, &err)) { @@ -175,7 +237,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; } @@ -211,9 +273,102 @@ bool HardwareAndroid::SetFirstActiveOmahaPingSent() { } 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; + if constexpr (!constants::kIsRecovery) { + 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; + } + } +} + +void HardwareAndroid::SetVbmetaDigestForInactiveSlot(bool reset) { + if constexpr (constants::kIsRecovery) { + return; + } + + if (android::base::GetProperty("ro.boot.avb_version", "").empty() && + android::base::GetProperty("ro.boot.vbmeta.avb_version", "").empty()) { + LOG(INFO) << "Device doesn't use avb, skipping setting vbmeta digest"; + return; + } + + if (reset) { + SetVbmetaDigestProp(""); + return; + } + + std::string digest = CalculateVbmetaDigestForInactiveSlot(); + if (digest.empty()) { + LOG(WARNING) << "Failed to calculate the vbmeta digest for the other slot"; + return; + } + SetVbmetaDigestProp(digest); +} + +string HardwareAndroid::GetVersionForLogging( + const string& partition_name) const { + if (partition_name == "boot") { + // ro.bootimage.build.date.utc + return GetPartitionBuildDate("bootimage"); + } + return GetPartitionBuildDate(partition_name); +} + +ErrorCode HardwareAndroid::IsPartitionUpdateValid( + const string& partition_name, const string& new_version) const { + if (partition_name == "boot") { + const auto old_version = GetPartitionBuildDate("bootimage"); + auto error_code = + IsTimestampNewerLogged(partition_name, old_version, new_version); + if (error_code == ErrorCode::kPayloadTimestampError) { + bool prevent_downgrade = + android::sysprop::GkiProperties::prevent_downgrade_version().value_or( + false); + if (!prevent_downgrade) { + LOG(WARNING) << "Downgrade of boot image is detected, but permitting " + "update because device does not prevent boot image " + "downgrade"; + // If prevent_downgrade_version sysprop is not explicitly set, permit + // downgrade in boot image version. + // Even though error_code is overridden here, always call + // IsTimestampNewerLogged to produce log messages. + error_code = ErrorCode::kSuccess; + } + } + return error_code; + } + + 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 = + IsTimestampNewerLogged(partition_name, old_version, new_version); + return error_code; +} + +// Mount options for non-system partitions. This option causes selinux treat +// every file in the mounted filesystem as having the 'postinstall_file' +// context, regardless of what the filesystem itself records. See "SELinux +// User's and Administrator's Guide" for more information on this option. +constexpr const char* kDefaultPostinstallMountOptions = + "context=u:object_r:postinstall_file:s0"; + +// Mount options for system partitions. This option causes selinux to use the +// 'postinstall_file' context as a fallback if there are no other selinux +// contexts associated with the file in the mounted partition. See "SELinux +// User's and Administrator's Guide" for more information on this option. +constexpr const char* kSystemPostinstallMountOptions = + "defcontext=u:object_r:postinstall_file:s0"; + +// Name of the system-partition +constexpr std::string_view kSystemPartitionName = "system"; + +const char* HardwareAndroid::GetPartitionMountOptions( + const std::string& partition_name) const { + if (partition_name == kSystemPartitionName) { + return kSystemPostinstallMountOptions; + } else { + return kDefaultPostinstallMountOptions; } } diff --git a/hardware_android.h b/aosp/hardware_android.h index 145a936d..d20e8df3 100644 --- a/hardware_android.h +++ b/aosp/hardware_android.h @@ -14,21 +14,23 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_HARDWARE_ANDROID_H_ -#define UPDATE_ENGINE_HARDWARE_ANDROID_H_ +#ifndef UPDATE_ENGINE_AOSP_HARDWARE_ANDROID_H_ +#define UPDATE_ENGINE_AOSP_HARDWARE_ANDROID_H_ #include <string> +#include <string_view> #include <base/macros.h> #include <base/time/time.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; @@ -40,15 +42,14 @@ class HardwareAndroid final : public HardwareInterface { bool IsOOBEEnabled() const override; bool IsOOBEComplete(base::Time* out_time_of_oobe) const override; std::string GetHardwareClass() const override; - std::string GetFirmwareVersion() const override; - std::string GetECVersion() const override; + std::string GetDeviceRequisition() const override; int GetMinKernelKeyVersion() const override; int GetMinFirmwareKeyVersion() const override; int GetMaxFirmwareKeyRollforward() const override; bool SetMaxFirmwareKeyRollforward(int firmware_max_rollforward) override; bool SetMaxKernelKeyRollforward(int kernel_max_rollforward) override; int GetPowerwashCount() const override; - bool SchedulePowerwash(bool is_rollback) override; + bool SchedulePowerwash(bool save_rollback_data) override; bool CancelPowerwash() override; bool GetNonVolatileDirectory(base::FilePath* path) const override; bool GetPowerwashSafeDirectory(base::FilePath* path) const override; @@ -57,6 +58,14 @@ class HardwareAndroid final : public HardwareInterface { bool GetFirstActiveOmahaPingSent() const override; bool SetFirstActiveOmahaPingSent() override; void SetWarmReset(bool warm_reset) override; + void SetVbmetaDigestForInactiveSlot(bool 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; + [[nodiscard]] const char* GetPartitionMountOptions( + const std::string& partition_name) const override; private: DISALLOW_COPY_AND_ASSIGN(HardwareAndroid); @@ -64,4 +73,4 @@ class HardwareAndroid final : public HardwareInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_HARDWARE_ANDROID_H_ +#endif // UPDATE_ENGINE_AOSP_HARDWARE_ANDROID_H_ diff --git a/logging_android.cc b/aosp/logging_android.cc index 0219075c..5ccf7bc5 100644 --- a/logging_android.cc +++ b/aosp/logging_android.cc @@ -240,9 +240,16 @@ bool RedirectToLiblog(int severity, 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()); + + if (priority == ANDROID_LOG_FATAL) { + // Abort the program for priority FATAL. __android_log_assert will log the + // message to stderr and CombinedLogger. + __android_log_assert(nullptr, nullptr, "%s", str.c_str()); + } else { + // 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; } diff --git a/metrics_reporter_android.cc b/aosp/metrics_reporter_android.cc index d8fa6e5b..a324fab1 100644 --- a/metrics_reporter_android.cc +++ b/aosp/metrics_reporter_android.cc @@ -14,10 +14,12 @@ // limitations under the License. // -#include "update_engine/metrics_reporter_android.h" +#include "update_engine/aosp/metrics_reporter_android.h" #include <stdint.h> +#include <algorithm> +#include <any> #include <memory> #include <string> @@ -30,6 +32,7 @@ #include <statslog.h> #include "update_engine/common/constants.h" +#include "update_engine/payload_consumer/install_plan.h" using android::fs_mgr::GetPartitionGroupName; using android::fs_mgr::LpMetadata; @@ -48,20 +51,38 @@ constexpr auto kMetricsReporterEnumOffset = 10000; int32_t GetStatsdEnumValue(int32_t value) { return kMetricsReporterEnumOffset + value; } + +bool IsHashTreeEnabled( + const chromeos_update_engine::InstallPlan* install_plan) { + return std::any_of( + install_plan->partitions.begin(), + install_plan->partitions.end(), + [](const auto& partition) { return partition.hash_tree_size > 0; }); +} + +bool IsFECEnabled(const chromeos_update_engine::InstallPlan* install_plan) { + return std::any_of( + install_plan->partitions.begin(), + install_plan->partitions.end(), + [](const auto& partition) { return partition.fec_size > 0; }); +} + } // namespace namespace chromeos_update_engine { namespace metrics { -std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter() { - return std::make_unique<MetricsReporterAndroid>(); +std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter( + DynamicPartitionControlInterface* dynamic_partition_control, + const InstallPlan* install_plan) { + return std::make_unique<MetricsReporterAndroid>(dynamic_partition_control, + install_plan); } } // namespace metrics void MetricsReporterAndroid::ReportUpdateAttemptMetrics( - SystemState* /* system_state */, int attempt_number, PayloadType payload_type, base::TimeDelta duration, @@ -106,6 +127,11 @@ void MetricsReporterAndroid::ReportUpdateAttemptMetrics( } } + bool vab_compression_enabled = android::base::GetBoolProperty( + "ro.virtual_ab.compression.enabled", false); + bool vab_compression_used = + dynamic_partition_control_->UpdateUsesSnapshotCompression(); + android::util::stats_write( android::util::UPDATE_ENGINE_UPDATE_ATTEMPT_REPORTED, attempt_number, @@ -118,7 +144,9 @@ void MetricsReporterAndroid::ReportUpdateAttemptMetrics( android::base::GetProperty("ro.build.fingerprint", "").c_str(), super_partition_size_bytes, slot_size_bytes, - super_free_space); + super_free_space, + vab_compression_enabled, + vab_compression_used); } void MetricsReporterAndroid::ReportUpdateAttemptDownloadMetrics( @@ -157,7 +185,9 @@ void MetricsReporterAndroid::ReportSuccessfulUpdateMetrics( 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)); + static_cast<int32_t>(reboot_count), + IsHashTreeEnabled(install_plan_), + IsFECEnabled(install_plan_)); } void MetricsReporterAndroid::ReportAbnormallyTerminatedUpdateAttemptMetrics() { diff --git a/metrics_reporter_android.h b/aosp/metrics_reporter_android.h index e320c122..aeb579a1 100644 --- a/metrics_reporter_android.h +++ b/aosp/metrics_reporter_android.h @@ -14,25 +14,28 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_METRICS_REPORTER_ANDROID_H_ -#define UPDATE_ENGINE_METRICS_REPORTER_ANDROID_H_ +#ifndef UPDATE_ENGINE_AOSP_METRICS_REPORTER_ANDROID_H_ +#define UPDATE_ENGINE_AOSP_METRICS_REPORTER_ANDROID_H_ #include <string> #include "update_engine/common/error_code.h" -#include "update_engine/metrics_constants.h" -#include "update_engine/metrics_reporter_interface.h" +#include "update_engine/common/metrics_constants.h" +#include "update_engine/common/metrics_reporter_interface.h" +#include "update_engine/payload_consumer/install_plan.h" namespace chromeos_update_engine { class MetricsReporterAndroid : public MetricsReporterInterface { public: - MetricsReporterAndroid() = default; + explicit MetricsReporterAndroid( + DynamicPartitionControlInterface* dynamic_partition_control, + const InstallPlan* install_plan) + : dynamic_partition_control_(dynamic_partition_control), + install_plan_(install_plan) {} ~MetricsReporterAndroid() override = default; - void Initialize() override {} - void ReportRollbackMetrics(metrics::RollbackResult result) override {} void ReportEnterpriseRollbackMetrics( @@ -41,13 +44,11 @@ class MetricsReporterAndroid : public MetricsReporterInterface { void ReportDailyMetrics(base::TimeDelta os_age) override {} void ReportUpdateCheckMetrics( - SystemState* system_state, metrics::CheckResult result, metrics::CheckReaction reaction, metrics::DownloadErrorCode download_error_code) override {} - void ReportUpdateAttemptMetrics(SystemState* system_state, - int attempt_number, + void ReportUpdateAttemptMetrics(int attempt_number, PayloadType payload_type, base::TimeDelta duration, base::TimeDelta duration_uptime, @@ -95,9 +96,12 @@ class MetricsReporterAndroid : public MetricsReporterInterface { bool has_time_restriction_policy, int time_to_update_days) override {} private: + DynamicPartitionControlInterface* dynamic_partition_control_{}; + const InstallPlan* install_plan_{}; + DISALLOW_COPY_AND_ASSIGN(MetricsReporterAndroid); }; } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_METRICS_REPORTER_ANDROID_H_ +#endif // UPDATE_ENGINE_AOSP_METRICS_REPORTER_ANDROID_H_ diff --git a/aosp/mock_dynamic_partition_control_android.h b/aosp/mock_dynamic_partition_control_android.h new file mode 100644 index 00000000..428b6c7a --- /dev/null +++ b/aosp/mock_dynamic_partition_control_android.h @@ -0,0 +1,146 @@ +// +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <stdint.h> + +#include <memory> +#include <set> +#include <string> + +#include <gmock/gmock.h> + +#include <libsnapshot/cow_writer.h> +#include <libsnapshot/snapshot_writer.h> + +#include "payload_consumer/file_descriptor.h" +#include "update_engine/aosp/dynamic_partition_control_android.h" +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/dynamic_partition_control_interface.h" + +namespace chromeos_update_engine { + +class MockDynamicPartitionControlAndroid + : public DynamicPartitionControlAndroid { + public: + MockDynamicPartitionControlAndroid() + : DynamicPartitionControlAndroid(0 /*source slot*/) {} + 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(FeatureFlag, GetVirtualAbCompressionFeatureFlag, (), (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)); + MOCK_METHOD(std::unique_ptr<android::snapshot::ISnapshotWriter>, + OpenCowWriter, + (const std::string& unsuffixed_partition_name, + const std::optional<std::string>& source_path, + bool is_append), + (override)); + MOCK_METHOD(FileDescriptorPtr, + OpenCowFd, + (const std::string& unsuffixed_partition_name, + const std::optional<std::string>& source_path, + bool is_append), + (override)); + MOCK_METHOD(bool, MapAllPartitions, (), (override)); + MOCK_METHOD(bool, UnmapAllPartitions, (), (override)); + MOCK_METHOD(bool, + IsDynamicPartition, + (const std::string&, uint32_t slot), + (override)); + MOCK_METHOD(bool, UpdateUsesSnapshotCompression, (), (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); + } + using DynamicPartitionControlAndroid::SetSourceSlot; + using DynamicPartitionControlAndroid::SetTargetSlot; +}; + +} // namespace chromeos_update_engine diff --git a/network_selector_android.cc b/aosp/network_selector_android.cc index 55ba7991..a7db4154 100644 --- a/network_selector_android.cc +++ b/aosp/network_selector_android.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/network_selector_android.h" +#include "update_engine/aosp/network_selector_android.h" #include <memory> @@ -25,14 +25,14 @@ namespace chromeos_update_engine { namespace network { -// Factory defined in network_selector.h. +// Factory defined in common/network_selector.h. std::unique_ptr<NetworkSelectorInterface> CreateNetworkSelector() { return std::make_unique<NetworkSelectorAndroid>(); } } // namespace network -// Defined in network_selector_interface.h. +// Defined in common/network_selector_interface.h. const NetworkId kDefaultNetworkId = NETWORK_UNSPECIFIED; bool NetworkSelectorAndroid::SetProcessNetwork(NetworkId network_id) { diff --git a/network_selector_android.h b/aosp/network_selector_android.h index 135536c2..b79d1b36 100644 --- a/network_selector_android.h +++ b/aosp/network_selector_android.h @@ -14,12 +14,12 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_NETWORK_SELECTOR_ANDROID_H_ -#define UPDATE_ENGINE_NETWORK_SELECTOR_ANDROID_H_ +#ifndef UPDATE_ENGINE_AOSP_NETWORK_SELECTOR_ANDROID_H_ +#define UPDATE_ENGINE_AOSP_NETWORK_SELECTOR_ANDROID_H_ #include <base/macros.h> -#include "update_engine/network_selector_interface.h" +#include "update_engine/common/network_selector_interface.h" namespace chromeos_update_engine { @@ -37,4 +37,4 @@ class NetworkSelectorAndroid final : public NetworkSelectorInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_NETWORK_SELECTOR_ANDROID_H_ +#endif // UPDATE_ENGINE_AOSP_NETWORK_SELECTOR_ANDROID_H_ diff --git a/common/platform_constants_android.cc b/aosp/platform_constants_android.cc index f468c3ba..a0a2a5ec 100644 --- a/common/platform_constants_android.cc +++ b/aosp/platform_constants_android.cc @@ -31,8 +31,6 @@ const char kCACertificatesPath[] = "/system/etc/security/cacerts_google"; // No deadline file API support on Android. const char kOmahaResponseDeadlineFile[] = ""; const char kNonVolatileDirectory[] = "/data/misc/update_engine"; -const char kPostinstallMountOptions[] = - "context=u:object_r:postinstall_file:s0"; } // namespace constants } // namespace chromeos_update_engine diff --git a/service_delegate_android_interface.h b/aosp/service_delegate_android_interface.h index 34a97123..3c287940 100644 --- a/service_delegate_android_interface.h +++ b/aosp/service_delegate_android_interface.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_SERVICE_DELEGATE_ANDROID_INTERFACE_H_ -#define UPDATE_ENGINE_SERVICE_DELEGATE_ANDROID_INTERFACE_H_ +#ifndef UPDATE_ENGINE_AOSP_SERVICE_DELEGATE_ANDROID_INTERFACE_H_ +#define UPDATE_ENGINE_AOSP_SERVICE_DELEGATE_ANDROID_INTERFACE_H_ #include <inttypes.h> @@ -124,4 +124,4 @@ class ServiceDelegateAndroidInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_SERVICE_DELEGATE_ANDROID_INTERFACE_H_ +#endif // UPDATE_ENGINE_AOSP_SERVICE_DELEGATE_ANDROID_INTERFACE_H_ diff --git a/sideload_main.cc b/aosp/sideload_main.cc index 27967cda..bf015c94 100644 --- a/sideload_main.cc +++ b/aosp/sideload_main.cc @@ -28,15 +28,15 @@ #include <brillo/streams/file_stream.h> #include <brillo/streams/stream.h> +#include "update_engine/aosp/update_attempter_android.h" #include "update_engine/common/boot_control.h" #include "update_engine/common/error_code_utils.h" #include "update_engine/common/hardware.h" +#include "update_engine/common/logging.h" #include "update_engine/common/prefs.h" #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; using std::vector; @@ -154,8 +154,11 @@ bool ApplyUpdatePayload(const string& payload, return false; } - UpdateAttempterAndroid update_attempter( - &sideload_daemon_state, &prefs, boot_control.get(), hardware.get()); + UpdateAttempterAndroid update_attempter(&sideload_daemon_state, + &prefs, + boot_control.get(), + hardware.get(), + nullptr); update_attempter.Init(); TEST_AND_RETURN_FALSE(update_attempter.ApplyPayload( diff --git a/update_attempter_android.cc b/aosp/update_attempter_android.cc index b7d119f2..4636c430 100644 --- a/update_attempter_android.cc +++ b/aosp/update_attempter_android.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/update_attempter_android.h" +#include "update_engine/aosp/update_attempter_android.h" #include <algorithm> #include <map> @@ -31,18 +31,18 @@ #include <brillo/strings/string_utils.h> #include <log/log_safetynet.h> -#include "update_engine/cleanup_previous_update_action.h" +#include "update_engine/aosp/cleanup_previous_update_action.h" #include "update_engine/common/constants.h" +#include "update_engine/common/daemon_state_interface.h" +#include "update_engine/common/download_action.h" #include "update_engine/common/error_code_utils.h" #include "update_engine/common/file_fetcher.h" +#include "update_engine/common/metrics_reporter_interface.h" +#include "update_engine/common/network_selector.h" #include "update_engine/common/utils.h" -#include "update_engine/daemon_state_interface.h" -#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" #include "update_engine/payload_consumer/file_descriptor_utils.h" #include "update_engine/payload_consumer/filesystem_verifier_action.h" @@ -64,7 +64,6 @@ using base::Bind; using base::Time; using base::TimeDelta; using base::TimeTicks; -using std::shared_ptr; using std::string; using std::vector; using update_engine::UpdateEngineStatus; @@ -133,14 +132,17 @@ UpdateAttempterAndroid::UpdateAttempterAndroid( DaemonStateInterface* daemon_state, PrefsInterface* prefs, BootControlInterface* boot_control, - HardwareInterface* hardware) + HardwareInterface* hardware, + std::unique_ptr<ApexHandlerInterface> apex_handler) : daemon_state_(daemon_state), prefs_(prefs), boot_control_(boot_control), hardware_(hardware), + apex_handler_android_(std::move(apex_handler)), processor_(new ActionProcessor()), clock_(new Clock()) { - metrics_reporter_ = metrics::CreateMetricsReporter(); + metrics_reporter_ = metrics::CreateMetricsReporter( + boot_control_->GetDynamicPartitionControl(), &install_plan_); network_selector_ = network::CreateNetworkSelector(); } @@ -181,7 +183,7 @@ bool UpdateAttempterAndroid::ApplyPayload( return LogAndSetError( error, FROM_HERE, "Already processing an update, cancel it first."); } - DCHECK(status_ == UpdateStatus::IDLE); + DCHECK_EQ(status_, UpdateStatus::IDLE); std::map<string, string> headers; if (!ParseKeyValuePairHeaders(key_value_pair_headers, &headers, error)) { @@ -317,6 +319,19 @@ bool UpdateAttempterAndroid::ApplyPayload( int64_t payload_size, const vector<string>& key_value_pair_headers, brillo::ErrorPtr* error) { + // update_engine state must be checked before modifying payload_fd_ otherwise + // already running update will be terminated (existing file descriptor will be + // closed) + if (status_ == UpdateStatus::UPDATED_NEED_REBOOT) { + return LogAndSetError( + error, FROM_HERE, "An update already applied, waiting for reboot"); + } + if (processor_->IsRunning()) { + return LogAndSetError( + error, FROM_HERE, "Already processing an update, cancel it first."); + } + DCHECK_EQ(status_, UpdateStatus::IDLE); + payload_fd_.reset(dup(fd)); const string payload_url = "fd://" + std::to_string(payload_fd_.get()); @@ -349,6 +364,12 @@ bool UpdateAttempterAndroid::ResetStatus(brillo::ErrorPtr* error) { LOG(INFO) << "Attempting to reset state from " << UpdateStatusToString(status_) << " to UpdateStatus::IDLE"; + if (apex_handler_android_ != nullptr) { + LOG(INFO) << "Cleaning up reserved space for compressed APEX (if any)"; + std::vector<ApexInfo> apex_infos_blank; + apex_handler_android_->AllocateSpace(apex_infos_blank); + } + switch (status_) { case UpdateStatus::IDLE: { if (!boot_control_->GetDynamicPartitionControl()->ResetUpdate(prefs_)) { @@ -374,6 +395,9 @@ bool UpdateAttempterAndroid::ResetStatus(brillo::ErrorPtr* error) { // Resets the warm reset property since we won't switch the slot. hardware_->SetWarmReset(false); + // Resets the vbmeta digest. + hardware_->SetVbmetaDigestForInactiveSlot(true /* reset */); + // Remove update progress for DeltaPerformer and remove snapshots. if (!boot_control_->GetDynamicPartitionControl()->ResetUpdate(prefs_)) ret_value = false; @@ -507,7 +531,7 @@ bool UpdateAttempterAndroid::VerifyPayloadApplicable( return LogAndSetError( error, FROM_HERE, "Failed to hash " + partition_path); } - if (!DeltaPerformer::ValidateSourceHash( + if (!PartitionWriter::ValidateSourceHash( source_hash, operation, fd, &errorcode)) { return false; } @@ -576,9 +600,9 @@ void UpdateAttempterAndroid::ActionCompleted(ActionProcessor* processor, cleanup_previous_update_code_ = code; NotifyCleanupPreviousUpdateCallbacksAndClear(); } - if (type == DownloadAction::StaticType()) { - download_progress_ = 0; - } + // 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; @@ -592,8 +616,11 @@ void UpdateAttempterAndroid::ActionCompleted(ActionProcessor* processor, 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); } } @@ -644,6 +671,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( @@ -725,15 +757,15 @@ void UpdateAttempterAndroid::BuildUpdateActions(HttpFetcher* fetcher) { std::make_unique<DownloadAction>(prefs_, boot_control_, hardware_, - nullptr, // system_state, not used. fetcher, // passes ownership 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 @@ -795,7 +827,6 @@ void UpdateAttempterAndroid::CollectAndReportUpdateMetricsOnUpdateFinished( TimeDelta duration_uptime = clock_->GetMonotonicTime() - monotonic_time_start; metrics_reporter_->ReportUpdateAttemptMetrics( - nullptr, // system_state static_cast<int>(attempt_number), payload_type, duration, @@ -859,20 +890,25 @@ void UpdateAttempterAndroid::UpdatePrefsAndReportUpdateMetricsOnReboot() { string current_version = android::base::GetProperty("ro.build.version.incremental", ""); TEST_AND_RETURN(!current_version.empty()); + const auto current_slot = boot_control_->GetCurrentSlot(); // If there's no record of previous version (e.g. due to a data wipe), we // save the info of current boot and skip the metrics report. if (!prefs_->Exists(kPrefsPreviousVersion)) { prefs_->SetString(kPrefsBootId, current_boot_id); prefs_->SetString(kPrefsPreviousVersion, current_version); + prefs_->SetInt64(std::string{kPrefsPreviousSlot}, + boot_control_->GetCurrentSlot()); ClearMetricsPrefs(); return; } + int64_t previous_slot = -1; + prefs_->GetInt64(kPrefsPreviousSlot, &previous_slot); string previous_version; - // update_engine restarted under the same build. + // update_engine restarted under the same build and same slot. // TODO(xunchang) identify and report rollback by checking UpdateMarker. if (prefs_->GetString(kPrefsPreviousVersion, &previous_version) && - previous_version == current_version) { + previous_version == current_version && previous_slot == current_slot) { string last_boot_id; bool is_reboot = prefs_->Exists(kPrefsBootId) && (prefs_->GetString(kPrefsBootId, &last_boot_id) && @@ -892,6 +928,8 @@ void UpdateAttempterAndroid::UpdatePrefsAndReportUpdateMetricsOnReboot() { // TODO(xunchang) check the build version is larger than the previous one. prefs_->SetString(kPrefsBootId, current_boot_id); prefs_->SetString(kPrefsPreviousVersion, current_version); + prefs_->SetInt64(std::string{kPrefsPreviousSlot}, + boot_control_->GetCurrentSlot()); bool previous_attempt_exists = prefs_->Exists(kPrefsPayloadAttemptNumber); // |kPrefsPayloadAttemptNumber| should be cleared upon successful update. @@ -954,6 +992,20 @@ uint64_t UpdateAttempterAndroid::AllocateSpaceForPayload( return 0; } + std::vector<ApexInfo> apex_infos(manifest.apex_info().begin(), + manifest.apex_info().end()); + uint64_t apex_size_required = 0; + if (apex_handler_android_ != nullptr) { + auto result = apex_handler_android_->CalculateSize(apex_infos); + if (!result.ok()) { + LogAndSetError(error, + FROM_HERE, + "Failed to calculate size required for compressed APEX"); + return 0; + } + apex_size_required = *result; + } + string payload_id = GetPayloadId(headers); uint64_t required_size = 0; if (!DeltaPerformer::PreparePartitionsForUpdate(prefs_, @@ -967,11 +1019,19 @@ uint64_t UpdateAttempterAndroid::AllocateSpaceForPayload( return 0; } else { LOG(ERROR) << "Insufficient space for payload: " << required_size + << " bytes, apex decompression: " << apex_size_required << " bytes"; - return required_size; + return required_size + apex_size_required; } } + if (apex_size_required > 0 && apex_handler_android_ != nullptr && + !apex_handler_android_->AllocateSpace(apex_infos)) { + LOG(ERROR) << "Insufficient space for apex decompression: " + << apex_size_required << " bytes"; + return apex_size_required; + } + LOG(INFO) << "Successfully allocated space for payload."; return 0; } diff --git a/update_attempter_android.h b/aosp/update_attempter_android.h index f8c78de1..70938bcd 100644 --- a/update_attempter_android.h +++ b/aosp/update_attempter_android.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_UPDATE_ATTEMPTER_ANDROID_H_ -#define UPDATE_ENGINE_UPDATE_ATTEMPTER_ANDROID_H_ +#ifndef UPDATE_ENGINE_AOSP_UPDATE_ATTEMPTER_ANDROID_H_ +#define UPDATE_ENGINE_AOSP_UPDATE_ATTEMPTER_ANDROID_H_ #include <stdint.h> @@ -26,20 +26,22 @@ #include <android-base/unique_fd.h> #include <base/time/time.h> +#include "update_engine/aosp/apex_handler_interface.h" +#include "update_engine/aosp/service_delegate_android_interface.h" #include "update_engine/client_library/include/update_engine/update_status.h" #include "update_engine/common/action_processor.h" #include "update_engine/common/boot_control_interface.h" #include "update_engine/common/clock.h" +#include "update_engine/common/daemon_state_interface.h" +#include "update_engine/common/download_action.h" #include "update_engine/common/hardware_interface.h" +#include "update_engine/common/metrics_reporter_interface.h" +#include "update_engine/common/network_selector_interface.h" #include "update_engine/common/prefs_interface.h" -#include "update_engine/daemon_state_interface.h" -#include "update_engine/metrics_reporter_interface.h" +#include "update_engine/common/service_observer_interface.h" #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" namespace chromeos_update_engine { @@ -47,6 +49,7 @@ class UpdateAttempterAndroid : public ServiceDelegateAndroidInterface, public ActionProcessorDelegate, public DownloadActionDelegate, + public FilesystemVerifyDelegate, public PostinstallRunnerAction::DelegateInterface, public CleanupPreviousUpdateActionDelegateInterface { public: @@ -55,7 +58,8 @@ class UpdateAttempterAndroid UpdateAttempterAndroid(DaemonStateInterface* daemon_state, PrefsInterface* prefs, BootControlInterface* boot_control_, - HardwareInterface* hardware_); + HardwareInterface* hardware_, + std::unique_ptr<ApexHandlerInterface> apex_handler); ~UpdateAttempterAndroid() override; // Further initialization to be done post construction. @@ -101,6 +105,9 @@ 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; @@ -200,6 +207,8 @@ class UpdateAttempterAndroid BootControlInterface* boot_control_; HardwareInterface* hardware_; + std::unique_ptr<ApexHandlerInterface> apex_handler_android_; + // Last status notification timestamp used for throttling. Use monotonic // TimeTicks to ensure that notifications are sent even if the system clock is // set back in the middle of an update. @@ -241,4 +250,4 @@ class UpdateAttempterAndroid } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_UPDATE_ATTEMPTER_ANDROID_H_ +#endif // UPDATE_ENGINE_AOSP_UPDATE_ATTEMPTER_ANDROID_H_ diff --git a/update_attempter_android_unittest.cc b/aosp/update_attempter_android_unittest.cc index 721b7352..f73df168 100644 --- a/update_attempter_android_unittest.cc +++ b/aosp/update_attempter_android_unittest.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/update_attempter_android.h" +#include "update_engine/aosp/update_attempter_android.h" #include <memory> #include <string> @@ -24,15 +24,16 @@ #include <base/time/time.h> #include <gtest/gtest.h> +#include "common/constants.h" +#include "update_engine/aosp/daemon_state_android.h" #include "update_engine/common/fake_boot_control.h" #include "update_engine/common/fake_clock.h" #include "update_engine/common/fake_hardware.h" #include "update_engine/common/fake_prefs.h" #include "update_engine/common/mock_action_processor.h" +#include "update_engine/common/mock_metrics_reporter.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" -#include "update_engine/daemon_state_android.h" -#include "update_engine/mock_metrics_reporter.h" using base::Time; using base::TimeDelta; @@ -63,14 +64,14 @@ class UpdateAttempterAndroidTest : public ::testing::Test { std::move(payload)); } - UpdateAttempterAndroid update_attempter_android_{ - &daemon_state_, &prefs_, &boot_control_, &hardware_}; - DaemonStateAndroid daemon_state_; FakePrefs prefs_; FakeBootControl boot_control_; FakeHardware hardware_; + UpdateAttempterAndroid update_attempter_android_{ + &daemon_state_, &prefs_, &boot_control_, &hardware_, nullptr}; + FakeClock* clock_; testing::NiceMock<MockMetricsReporter>* metrics_reporter_; }; @@ -81,6 +82,8 @@ TEST_F(UpdateAttempterAndroidTest, UpdatePrefsSameBuildVersionOnInit) { prefs_.SetString(kPrefsPreviousVersion, build_version); prefs_.SetString(kPrefsBootId, "oldboot"); prefs_.SetInt64(kPrefsNumReboots, 1); + prefs_.SetInt64(kPrefsPreviousSlot, 1); + boot_control_.SetCurrentSlot(1); EXPECT_CALL(*metrics_reporter_, ReportTimeToReboot(_)).Times(0); update_attempter_android_.Init(); @@ -88,15 +91,15 @@ TEST_F(UpdateAttempterAndroidTest, UpdatePrefsSameBuildVersionOnInit) { // Check that the boot_id and reboot_count are updated. std::string boot_id; utils::GetBootId(&boot_id); - EXPECT_TRUE(prefs_.Exists(kPrefsBootId)); + ASSERT_TRUE(prefs_.Exists(kPrefsBootId)); std::string prefs_boot_id; - EXPECT_TRUE(prefs_.GetString(kPrefsBootId, &prefs_boot_id)); - EXPECT_EQ(boot_id, prefs_boot_id); + ASSERT_TRUE(prefs_.GetString(kPrefsBootId, &prefs_boot_id)); + ASSERT_EQ(boot_id, prefs_boot_id); - EXPECT_TRUE(prefs_.Exists(kPrefsNumReboots)); + ASSERT_TRUE(prefs_.Exists(kPrefsNumReboots)); int64_t reboot_count; - EXPECT_TRUE(prefs_.GetInt64(kPrefsNumReboots, &reboot_count)); - EXPECT_EQ(2, reboot_count); + ASSERT_TRUE(prefs_.GetInt64(kPrefsNumReboots, &reboot_count)); + ASSERT_EQ(2, reboot_count); } TEST_F(UpdateAttempterAndroidTest, UpdatePrefsBuildVersionChangeOnInit) { @@ -138,8 +141,7 @@ TEST_F(UpdateAttempterAndroidTest, ReportMetricsOnUpdateTerminated) { TimeDelta duration_uptime = up_time - Time::FromInternalValue(12345); EXPECT_CALL( *metrics_reporter_, - ReportUpdateAttemptMetrics(_, - 2, + ReportUpdateAttemptMetrics(2, _, duration, duration_uptime, diff --git a/update_engine_client_android.cc b/aosp/update_engine_client_android.cc index 1a68cf49..1a68cf49 100644 --- a/update_engine_client_android.cc +++ b/aosp/update_engine_client_android.cc diff --git a/binder_bindings/android/brillo/IUpdateEngine.aidl b/binder_bindings/android/brillo/IUpdateEngine.aidl deleted file mode 100644 index 56e15246..00000000 --- a/binder_bindings/android/brillo/IUpdateEngine.aidl +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2016 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.brillo; - -import android.brillo.IUpdateEngineStatusCallback; -import android.brillo.ParcelableUpdateEngineStatus; - -interface IUpdateEngine { - void SetUpdateAttemptFlags(in int flags); - boolean AttemptUpdate(in String app_version, in String omaha_url, in int flags); - void AttemptRollback(in boolean powerwash); - boolean CanRollback(); - void ResetStatus(); - ParcelableUpdateEngineStatus GetStatus(); - void RebootIfNeeded(); - void SetChannel(in String target_channel, in boolean powewash); - String GetChannel(in boolean get_current_channel); - void SetCohortHint(in String cohort_hint); - String GetCohortHint(); - void SetP2PUpdatePermission(in boolean enabled); - boolean GetP2PUpdatePermission(); - void SetUpdateOverCellularPermission(in boolean enabled); - void SetUpdateOverCellularTarget(in String target_version, - in long target_size); - boolean GetUpdateOverCellularPermission(); - long GetDurationSinceUpdate(); - String GetPrevVersion(); - String GetRollbackPartition(); - void RegisterStatusCallback(in IUpdateEngineStatusCallback callback); - int GetLastAttemptError(); - int GetEolStatus(); -} diff --git a/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl b/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl deleted file mode 100644 index 837d44d5..00000000 --- a/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2016 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.brillo; - -import android.brillo.ParcelableUpdateEngineStatus; - -interface IUpdateEngineStatusCallback { - oneway - void HandleStatusUpdate(in ParcelableUpdateEngineStatus status); -} diff --git a/binder_bindings/android/brillo/ParcelableUpdateEngineStatus.aidl b/binder_bindings/android/brillo/ParcelableUpdateEngineStatus.aidl deleted file mode 100644 index fc10505b..00000000 --- a/binder_bindings/android/brillo/ParcelableUpdateEngineStatus.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2016 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.brillo; - -parcelable ParcelableUpdateEngineStatus cpp_header - "update_engine/parcelable_update_engine_status.h"; diff --git a/binder_service_brillo.cc b/binder_service_brillo.cc deleted file mode 100644 index cc747639..00000000 --- a/binder_service_brillo.cc +++ /dev/null @@ -1,247 +0,0 @@ -// -// Copyright (C) 2015 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_brillo.h" - -#include <base/bind.h> - -#include <binderwrapper/binder_wrapper.h> - -#include <utils/String16.h> -#include <utils/StrongPointer.h> - -#include "update_engine/update_status_utils.h" - -using android::sp; -using android::String16; -using android::String8; -using android::binder::Status; -using android::brillo::IUpdateEngineStatusCallback; -using android::brillo::ParcelableUpdateEngineStatus; -using brillo::ErrorPtr; -using std::string; -using update_engine::UpdateEngineStatus; - -namespace chromeos_update_engine { - -namespace { -string NormalString(const String16& in) { - return string{String8{in}.string()}; -} - -Status ToStatus(ErrorPtr* error) { - return Status::fromServiceSpecificError( - 1, String8{error->get()->GetMessage().c_str()}); -} -} // namespace - -template <typename... Parameters, typename... Arguments> -Status BinderUpdateEngineBrilloService::CallCommonHandler( - bool (UpdateEngineService::*Handler)(ErrorPtr*, Parameters...), - Arguments... arguments) { - ErrorPtr error; - if (((common_.get())->*Handler)(&error, arguments...)) - return Status::ok(); - return ToStatus(&error); -} - -Status BinderUpdateEngineBrilloService::SetUpdateAttemptFlags(int flags) { - return CallCommonHandler(&UpdateEngineService::SetUpdateAttemptFlags, flags); -} - -Status BinderUpdateEngineBrilloService::AttemptUpdate( - const String16& app_version, - const String16& omaha_url, - int flags, - bool* out_result) { - return CallCommonHandler(&UpdateEngineService::AttemptUpdate, - NormalString(app_version), - NormalString(omaha_url), - flags, - out_result); -} - -Status BinderUpdateEngineBrilloService::AttemptRollback(bool powerwash) { - return CallCommonHandler(&UpdateEngineService::AttemptRollback, powerwash); -} - -Status BinderUpdateEngineBrilloService::CanRollback(bool* out_can_rollback) { - return CallCommonHandler(&UpdateEngineService::CanRollback, out_can_rollback); -} - -Status BinderUpdateEngineBrilloService::ResetStatus() { - return CallCommonHandler(&UpdateEngineService::ResetStatus); -} - -Status BinderUpdateEngineBrilloService::GetStatus( - ParcelableUpdateEngineStatus* status) { - UpdateEngineStatus update_engine_status; - auto ret = - CallCommonHandler(&UpdateEngineService::GetStatus, &update_engine_status); - - if (ret.isOk()) { - *status = ParcelableUpdateEngineStatus(update_engine_status); - } - - return ret; -} - -Status BinderUpdateEngineBrilloService::RebootIfNeeded() { - return CallCommonHandler(&UpdateEngineService::RebootIfNeeded); -} - -Status BinderUpdateEngineBrilloService::SetChannel( - const String16& target_channel, bool powerwash) { - return CallCommonHandler(&UpdateEngineService::SetChannel, - NormalString(target_channel), - powerwash); -} - -Status BinderUpdateEngineBrilloService::GetChannel(bool get_current_channel, - String16* out_channel) { - string channel_string; - auto ret = CallCommonHandler( - &UpdateEngineService::GetChannel, get_current_channel, &channel_string); - - *out_channel = String16(channel_string.c_str()); - return ret; -} - -Status BinderUpdateEngineBrilloService::SetCohortHint( - const String16& in_cohort_hint) { - return CallCommonHandler(&UpdateEngineService::SetCohortHint, - NormalString(in_cohort_hint)); -} - -Status BinderUpdateEngineBrilloService::GetCohortHint( - String16* out_cohort_hint) { - string cohort_hint; - auto ret = - CallCommonHandler(&UpdateEngineService::GetCohortHint, &cohort_hint); - - *out_cohort_hint = String16(cohort_hint.c_str()); - return ret; -} - -Status BinderUpdateEngineBrilloService::SetP2PUpdatePermission(bool enabled) { - return CallCommonHandler(&UpdateEngineService::SetP2PUpdatePermission, - enabled); -} - -Status BinderUpdateEngineBrilloService::GetP2PUpdatePermission( - bool* out_p2p_permission) { - return CallCommonHandler(&UpdateEngineService::GetP2PUpdatePermission, - out_p2p_permission); -} - -Status BinderUpdateEngineBrilloService::SetUpdateOverCellularPermission( - bool enabled) { - return CallCommonHandler( - &UpdateEngineService::SetUpdateOverCellularPermission, enabled); -} - -Status BinderUpdateEngineBrilloService::SetUpdateOverCellularTarget( - const String16& target_version, int64_t target_size) { - return CallCommonHandler(&UpdateEngineService::SetUpdateOverCellularTarget, - NormalString(target_version), - target_size); -} - -Status BinderUpdateEngineBrilloService::GetUpdateOverCellularPermission( - bool* out_cellular_permission) { - return CallCommonHandler( - &UpdateEngineService::GetUpdateOverCellularPermission, - out_cellular_permission); -} - -Status BinderUpdateEngineBrilloService::GetDurationSinceUpdate( - int64_t* out_duration) { - return CallCommonHandler(&UpdateEngineService::GetDurationSinceUpdate, - out_duration); -} - -Status BinderUpdateEngineBrilloService::GetPrevVersion( - String16* out_prev_version) { - string version_string; - auto ret = - CallCommonHandler(&UpdateEngineService::GetPrevVersion, &version_string); - - *out_prev_version = String16(version_string.c_str()); - return ret; -} - -Status BinderUpdateEngineBrilloService::GetRollbackPartition( - String16* out_rollback_partition) { - string partition_string; - auto ret = CallCommonHandler(&UpdateEngineService::GetRollbackPartition, - &partition_string); - - if (ret.isOk()) { - *out_rollback_partition = String16(partition_string.c_str()); - } - - return ret; -} - -Status BinderUpdateEngineBrilloService::RegisterStatusCallback( - const sp<IUpdateEngineStatusCallback>& callback) { - callbacks_.emplace_back(callback); - - auto binder_wrapper = android::BinderWrapper::Get(); - - binder_wrapper->RegisterForDeathNotifications( - IUpdateEngineStatusCallback::asBinder(callback), - base::Bind(&BinderUpdateEngineBrilloService::UnregisterStatusCallback, - base::Unretained(this), - base::Unretained(callback.get()))); - - return Status::ok(); -} - -Status BinderUpdateEngineBrilloService::GetLastAttemptError( - int* out_last_attempt_error) { - return CallCommonHandler(&UpdateEngineService::GetLastAttemptError, - out_last_attempt_error); -} - -Status BinderUpdateEngineBrilloService::GetEolStatus(int* out_eol_status) { - return CallCommonHandler(&UpdateEngineService::GetEolStatus, out_eol_status); -} - -void BinderUpdateEngineBrilloService::UnregisterStatusCallback( - IUpdateEngineStatusCallback* callback) { - auto it = callbacks_.begin(); - while (it != callbacks_.end() && it->get() != callback) - it++; - - if (it == callbacks_.end()) { - LOG(ERROR) << "Got death notification for unknown callback."; - return; - } - - LOG(INFO) << "Erasing orphan callback"; - callbacks_.erase(it); -} - -void BinderUpdateEngineBrilloService::SendStatusUpdate( - const UpdateEngineStatus& update_engine_status) { - ParcelableUpdateEngineStatus parcelable_status(update_engine_status); - for (auto& callback : callbacks_) { - callback->HandleStatusUpdate(parcelable_status); - } -} - -} // namespace chromeos_update_engine diff --git a/binder_service_brillo.h b/binder_service_brillo.h deleted file mode 100644 index d0d0dc92..00000000 --- a/binder_service_brillo.h +++ /dev/null @@ -1,114 +0,0 @@ -// -// Copyright (C) 2016 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_BRILLO_H_ -#define UPDATE_ENGINE_BINDER_SERVICE_BRILLO_H_ - -#include <utils/Errors.h> - -#include <memory> -#include <string> -#include <vector> - -#include <utils/RefBase.h> - -#include "update_engine/common_service.h" -#include "update_engine/parcelable_update_engine_status.h" -#include "update_engine/service_observer_interface.h" - -#include "android/brillo/BnUpdateEngine.h" -#include "android/brillo/IUpdateEngineStatusCallback.h" - -namespace chromeos_update_engine { - -class BinderUpdateEngineBrilloService : public android::brillo::BnUpdateEngine, - public ServiceObserverInterface { - public: - explicit BinderUpdateEngineBrilloService(SystemState* system_state) - : common_(new UpdateEngineService(system_state)) {} - virtual ~BinderUpdateEngineBrilloService() = default; - - const char* ServiceName() const { - return "android.brillo.UpdateEngineService"; - } - - // ServiceObserverInterface overrides. - void SendStatusUpdate( - const update_engine::UpdateEngineStatus& update_engine_status) override; - void SendPayloadApplicationComplete(ErrorCode error_code) override {} - - // android::brillo::BnUpdateEngine overrides. - android::binder::Status SetUpdateAttemptFlags(int flags) override; - android::binder::Status AttemptUpdate(const android::String16& app_version, - const android::String16& omaha_url, - int flags, - bool* out_result) override; - android::binder::Status AttemptRollback(bool powerwash) override; - android::binder::Status CanRollback(bool* out_can_rollback) override; - android::binder::Status ResetStatus() override; - android::binder::Status GetStatus( - android::brillo::ParcelableUpdateEngineStatus* status); - android::binder::Status RebootIfNeeded() override; - android::binder::Status SetChannel(const android::String16& target_channel, - bool powerwash) override; - android::binder::Status GetChannel(bool get_current_channel, - android::String16* out_channel) override; - android::binder::Status SetCohortHint( - const android::String16& cohort_hint) override; - android::binder::Status GetCohortHint( - android::String16* out_cohort_hint) override; - android::binder::Status SetP2PUpdatePermission(bool enabled) override; - android::binder::Status GetP2PUpdatePermission( - bool* out_p2p_permission) override; - android::binder::Status SetUpdateOverCellularPermission( - bool enabled) override; - android::binder::Status SetUpdateOverCellularTarget( - const android::String16& target_version, int64_t target_size) override; - android::binder::Status GetUpdateOverCellularPermission( - bool* out_cellular_permission) override; - android::binder::Status GetDurationSinceUpdate( - int64_t* out_duration) override; - android::binder::Status GetPrevVersion( - android::String16* out_prev_version) override; - android::binder::Status GetRollbackPartition( - android::String16* out_rollback_partition) override; - android::binder::Status RegisterStatusCallback( - const android::sp<android::brillo::IUpdateEngineStatusCallback>& callback) - override; - android::binder::Status GetLastAttemptError( - int* out_last_attempt_error) override; - android::binder::Status GetEolStatus(int* out_eol_status) override; - - private: - // Generic function for dispatching to the common service. - template <typename... Parameters, typename... Arguments> - android::binder::Status CallCommonHandler( - bool (UpdateEngineService::*Handler)(brillo::ErrorPtr*, Parameters...), - Arguments... arguments); - - // To be used as a death notification handler only. - void UnregisterStatusCallback( - android::brillo::IUpdateEngineStatusCallback* callback); - - std::unique_ptr<UpdateEngineService> common_; - - std::vector<android::sp<android::brillo::IUpdateEngineStatusCallback>> - callbacks_; -}; - -} // namespace chromeos_update_engine - -#endif // UPDATE_ENGINE_BINDER_SERVICE_BRILLO_H_ diff --git a/client-headers/BUILD.gn b/client-headers/BUILD.gn new file mode 100644 index 00000000..8c1a17ea --- /dev/null +++ b/client-headers/BUILD.gn @@ -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. +# + +import("//common-mk/generate-dbus-proxies.gni") + +group("all") { + deps = [ ":libupdate_engine-client-headers" ] +} + +# update_engine client library generated headers. Used by other daemons and +# by the update_engine_client console program to interact with update_engine. +generate_dbus_proxies("libupdate_engine-client-headers") { + sources = [ "../dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml" ] + dbus_service_config = "../dbus_bindings/dbus-service-config.json" + mock_output_file = "include/update_engine/dbus-proxy-mocks.h" + proxy_output_file = "include/update_engine/dbus-proxies.h" + proxy_path_in_mocks = "update_engine/dbus-proxies.h" +} diff --git a/libupdate_engine-client-test.pc.in b/client-headers/libupdate_engine-client-test.pc.in index 92a4af36..92a4af36 100644 --- a/libupdate_engine-client-test.pc.in +++ b/client-headers/libupdate_engine-client-test.pc.in diff --git a/libupdate_engine-client.pc.in b/client-headers/libupdate_engine-client.pc.in index 4c87e1d8..4c87e1d8 100644 --- a/libupdate_engine-client.pc.in +++ b/client-headers/libupdate_engine-client.pc.in diff --git a/client_library/client.cc b/client_library/client.cc deleted file mode 100644 index b05df90b..00000000 --- a/client_library/client.cc +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (C) 2015 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/client_library/include/update_engine/client.h" - -#include <memory> - -#if USE_BINDER -#include "update_engine/client_library/client_binder.h" -#else // !USE_BINDER -#include "update_engine/client_library/client_dbus.h" -#endif // USE_BINDER - -using std::unique_ptr; - -namespace update_engine { - -unique_ptr<UpdateEngineClient> UpdateEngineClient::CreateInstance() { -#if USE_BINDER - auto update_engine_client_impl = new internal::BinderUpdateEngineClient{}; -#else // !USE_BINDER - auto update_engine_client_impl = new internal::DBusUpdateEngineClient{}; -#endif // USE_BINDER - auto ret = unique_ptr<UpdateEngineClient>{update_engine_client_impl}; - - if (!update_engine_client_impl->Init()) { - ret.reset(); - } - - return ret; -} - -} // namespace update_engine diff --git a/client_library/client_binder.cc b/client_library/client_binder.cc deleted file mode 100644 index 588bc64b..00000000 --- a/client_library/client_binder.cc +++ /dev/null @@ -1,264 +0,0 @@ -// -// Copyright (C) 2015 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/client_library/client_binder.h" - -#include <binder/IServiceManager.h> - -#include <base/message_loop/message_loop.h> -#include <utils/String8.h> - -#include "update_engine/common_service.h" -#include "update_engine/parcelable_update_engine_status.h" -#include "update_engine/update_status_utils.h" - -using android::getService; -using android::OK; -using android::String16; -using android::String8; -using android::binder::Status; -using android::brillo::ParcelableUpdateEngineStatus; -using chromeos_update_engine::StringToUpdateStatus; -using std::string; -using update_engine::UpdateAttemptFlags; - -namespace update_engine { -namespace internal { - -bool BinderUpdateEngineClient::Init() { - if (!binder_watcher_.Init()) - return false; - - return getService(String16{"android.brillo.UpdateEngineService"}, - &service_) == OK; -} - -bool BinderUpdateEngineClient::AttemptUpdate(const string& in_app_version, - const string& in_omaha_url, - bool at_user_request) { - bool started; - return service_ - ->AttemptUpdate( - String16{in_app_version.c_str()}, - String16{in_omaha_url.c_str()}, - at_user_request ? 0 : UpdateAttemptFlags::kFlagNonInteractive, - &started) - .isOk(); -} - -bool BinderUpdateEngineClient::AttemptInstall( - const string& omaha_url, const std::vector<string>& dlc_module_ids) { - return false; -} - -bool BinderUpdateEngineClient::GetStatus(int64_t* out_last_checked_time, - double* out_progress, - UpdateStatus* out_update_status, - string* out_new_version, - int64_t* out_new_size) const { - ParcelableUpdateEngineStatus status; - - if (!service_->GetStatus(&status).isOk()) - return false; - - *out_last_checked_time = status.last_checked_time_; - *out_progress = status.progress_; - StringToUpdateStatus(String8{status.current_operation_}.string(), - out_update_status); - *out_new_version = String8{status.new_version_}.string(); - *out_new_size = status.new_size_; - return true; -} - -bool BinderUpdateEngineClient::SetCohortHint(const string& in_cohort_hint) { - return service_->SetCohortHint(String16{in_cohort_hint.c_str()}).isOk(); -} - -bool BinderUpdateEngineClient::GetCohortHint(string* out_cohort_hint) const { - String16 out_as_string16; - - if (!service_->GetCohortHint(&out_as_string16).isOk()) - return false; - - *out_cohort_hint = String8{out_as_string16}.string(); - return true; -} - -bool BinderUpdateEngineClient::SetUpdateOverCellularPermission(bool allowed) { - return service_->SetUpdateOverCellularPermission(allowed).isOk(); -} - -bool BinderUpdateEngineClient::GetUpdateOverCellularPermission( - bool* allowed) const { - return service_->GetUpdateOverCellularPermission(allowed).isOk(); -} - -bool BinderUpdateEngineClient::SetP2PUpdatePermission(bool enabled) { - return service_->SetP2PUpdatePermission(enabled).isOk(); -} - -bool BinderUpdateEngineClient::GetP2PUpdatePermission(bool* enabled) const { - return service_->GetP2PUpdatePermission(enabled).isOk(); -} - -bool BinderUpdateEngineClient::Rollback(bool powerwash) { - return service_->AttemptRollback(powerwash).isOk(); -} - -bool BinderUpdateEngineClient::GetRollbackPartition( - string* rollback_partition) const { - String16 out_as_string16; - - if (!service_->GetRollbackPartition(&out_as_string16).isOk()) - return false; - - *rollback_partition = String8{out_as_string16}.string(); - return true; -} - -bool BinderUpdateEngineClient::GetPrevVersion(string* prev_version) const { - String16 out_as_string16; - - if (!service_->GetPrevVersion(&out_as_string16).isOk()) - return false; - - *prev_version = String8{out_as_string16}.string(); - return true; -} - -void BinderUpdateEngineClient::RebootIfNeeded() { - if (!service_->RebootIfNeeded().isOk()) { - // Reboot error code doesn't necessarily mean that a reboot - // failed. For example, D-Bus may be shutdown before we receive the - // result. - LOG(INFO) << "RebootIfNeeded() failure ignored."; - } -} - -bool BinderUpdateEngineClient::ResetStatus() { - return service_->ResetStatus().isOk(); -} - -Status BinderUpdateEngineClient::StatusUpdateCallback::HandleStatusUpdate( - const ParcelableUpdateEngineStatus& status) { - UpdateStatus update_status; - - StringToUpdateStatus(String8{status.current_operation_}.string(), - &update_status); - - for (auto& handler : client_->handlers_) { - handler->HandleStatusUpdate(status.last_checked_time_, - status.progress_, - update_status, - String8{status.new_version_}.string(), - status.new_size_); - } - - return Status::ok(); -} - -bool BinderUpdateEngineClient::RegisterStatusUpdateHandler( - StatusUpdateHandler* handler) { - if (!status_callback_.get()) { - status_callback_ = new BinderUpdateEngineClient::StatusUpdateCallback(this); - if (!service_->RegisterStatusCallback(status_callback_).isOk()) { - return false; - } - } - - handlers_.push_back(handler); - - int64_t last_checked_time; - double progress; - UpdateStatus update_status; - string new_version; - int64_t new_size; - - if (!GetStatus(&last_checked_time, - &progress, - &update_status, - &new_version, - &new_size)) { - handler->IPCError("Could not get status from binder service"); - } - - handler->HandleStatusUpdate( - last_checked_time, progress, update_status, new_version, new_size); - - return true; -} - -bool BinderUpdateEngineClient::UnregisterStatusUpdateHandler( - StatusUpdateHandler* handler) { - auto it = std::find(handlers_.begin(), handlers_.end(), handler); - if (it != handlers_.end()) { - handlers_.erase(it); - return true; - } - - return false; -} - -bool BinderUpdateEngineClient::SetTargetChannel(const string& in_target_channel, - bool allow_powerwash) { - return service_ - ->SetChannel(String16{in_target_channel.c_str()}, allow_powerwash) - .isOk(); -} - -bool BinderUpdateEngineClient::GetTargetChannel(string* out_channel) const { - String16 out_as_string16; - - if (!service_->GetChannel(false, &out_as_string16).isOk()) - return false; - - *out_channel = String8{out_as_string16}.string(); - return true; -} - -bool BinderUpdateEngineClient::GetChannel(string* out_channel) const { - String16 out_as_string16; - - if (!service_->GetChannel(true, &out_as_string16).isOk()) - return false; - - *out_channel = String8{out_as_string16}.string(); - return true; -} - -bool BinderUpdateEngineClient::GetLastAttemptError( - int32_t* last_attempt_error) const { - int out_as_int; - - if (!service_->GetLastAttemptError(&out_as_int).isOk()) - return false; - - *last_attempt_error = out_as_int; - return true; -} - -bool BinderUpdateEngineClient::GetEolStatus(int32_t* eol_status) const { - int out_as_int; - - if (!service_->GetEolStatus(&out_as_int).isOk()) - return false; - - *eol_status = out_as_int; - return true; -} - -} // namespace internal -} // namespace update_engine diff --git a/client_library/client_binder.h b/client_library/client_binder.h deleted file mode 100644 index f3e41026..00000000 --- a/client_library/client_binder.h +++ /dev/null @@ -1,117 +0,0 @@ -// -// Copyright (C) 2016 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_CLIENT_LIBRARY_CLIENT_BINDER_H_ -#define UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_BINDER_H_ - -#include <cstdint> -#include <memory> -#include <string> -#include <vector> - -#include <base/macros.h> -#include <utils/String16.h> -#include <utils/StrongPointer.h> - -#include <brillo/binder_watcher.h> - -#include "android/brillo/BnUpdateEngineStatusCallback.h" -#include "android/brillo/IUpdateEngine.h" - -#include "update_engine/client_library/include/update_engine/client.h" - -namespace update_engine { -namespace internal { - -class BinderUpdateEngineClient : public UpdateEngineClient { - public: - BinderUpdateEngineClient() = default; - bool Init(); - - virtual ~BinderUpdateEngineClient() = default; - - bool AttemptUpdate(const std::string& app_version, - const std::string& omaha_url, - bool at_user_request) override; - - bool AttemptInstall(const std::string& omaha_url, - const std::vector<std::string>& dlc_module_ids) override; - - bool GetStatus(int64_t* out_last_checked_time, - double* out_progress, - UpdateStatus* out_update_status, - std::string* out_new_version, - int64_t* out_new_size) const override; - - bool SetCohortHint(const std::string& in_cohort_hint) override; - bool GetCohortHint(std::string* out_cohort_hint) const override; - - bool SetUpdateOverCellularPermission(bool allowed) override; - bool GetUpdateOverCellularPermission(bool* allowed) const override; - - bool SetP2PUpdatePermission(bool enabled) override; - bool GetP2PUpdatePermission(bool* enabled) const override; - - bool Rollback(bool powerwash) override; - - bool GetRollbackPartition(std::string* rollback_partition) const override; - - void RebootIfNeeded() override; - - bool GetPrevVersion(std::string* prev_version) const override; - - bool ResetStatus() override; - - bool SetTargetChannel(const std::string& target_channel, - bool allow_powerwash) override; - - bool GetTargetChannel(std::string* out_channel) const override; - - bool GetChannel(std::string* out_channel) const override; - - bool RegisterStatusUpdateHandler(StatusUpdateHandler* handler) override; - bool UnregisterStatusUpdateHandler(StatusUpdateHandler* handler) override; - - bool GetLastAttemptError(int32_t* last_attempt_error) const override; - - bool GetEolStatus(int32_t* eol_status) const override; - - private: - class StatusUpdateCallback - : public android::brillo::BnUpdateEngineStatusCallback { - public: - explicit StatusUpdateCallback(BinderUpdateEngineClient* client) - : client_(client) {} - - android::binder::Status HandleStatusUpdate( - const android::brillo::ParcelableUpdateEngineStatus& status) override; - - private: - BinderUpdateEngineClient* client_; - }; - - android::sp<android::brillo::IUpdateEngine> service_; - android::sp<android::brillo::IUpdateEngineStatusCallback> status_callback_; - std::vector<update_engine::StatusUpdateHandler*> handlers_; - brillo::BinderWatcher binder_watcher_; - - DISALLOW_COPY_AND_ASSIGN(BinderUpdateEngineClient); -}; // class BinderUpdateEngineClient - -} // namespace internal -} // namespace update_engine - -#endif // UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_BINDER_H_ diff --git a/client_library/client_dbus.cc b/client_library/client_dbus.cc index 7ca63070..30ad78c6 100644 --- a/client_library/client_dbus.cc +++ b/client_library/client_dbus.cc @@ -16,23 +16,51 @@ #include "update_engine/client_library/client_dbus.h" -#include <base/message_loop/message_loop.h> +#include <base/message_loop/message_loop_current.h> + +#include <memory> #include <dbus/bus.h> #include <update_engine/dbus-constants.h> -#include <update_engine/proto_bindings/update_engine.pb.h> #include "update_engine/update_status_utils.h" -using chromeos_update_engine::StringToUpdateStatus; using dbus::Bus; using org::chromium::UpdateEngineInterfaceProxy; using std::string; +using std::unique_ptr; using std::vector; namespace update_engine { + +unique_ptr<UpdateEngineClient> UpdateEngineClient::CreateInstance() { + auto ret = std::make_unique<internal::DBusUpdateEngineClient>(); + if (!ret->Init()) { + ret.reset(); + } + return ret; +} + namespace internal { +namespace { +// This converts the status from Protobuf |StatusResult| to The internal +// |UpdateEngineStatus| struct. +void ConvertToUpdateEngineStatus(const StatusResult& status, + UpdateEngineStatus* out_status) { + out_status->last_checked_time = status.last_checked_time(); + out_status->progress = status.progress(); + out_status->new_version = status.new_version(); + out_status->new_size_bytes = status.new_size(); + out_status->status = static_cast<UpdateStatus>(status.current_operation()); + out_status->is_enterprise_rollback = status.is_enterprise_rollback(); + out_status->is_install = status.is_install(); + out_status->eol_date = status.eol_date(); + out_status->will_powerwash_after_reboot = + status.will_powerwash_after_reboot(); +} +} // namespace + bool DBusUpdateEngineClient::Init() { Bus::Options options; options.bus_type = Bus::SYSTEM; @@ -57,41 +85,24 @@ bool DBusUpdateEngineClient::AttemptUpdate(const string& in_app_version, nullptr); } -bool DBusUpdateEngineClient::AttemptInstall( - const string& omaha_url, const vector<string>& dlc_module_ids) { - // Convert parameters into protobuf. - chromeos_update_engine::DlcParameters dlc_parameters; - dlc_parameters.set_omaha_url(omaha_url); - for (const auto& dlc_module_id : dlc_module_ids) { - chromeos_update_engine::DlcInfo* dlc_info = dlc_parameters.add_dlc_infos(); - dlc_info->set_dlc_id(dlc_module_id); - } - string dlc_request; - if (dlc_parameters.SerializeToString(&dlc_request)) { - return proxy_->AttemptInstall(dlc_request, nullptr /* brillo::ErrorPtr* */); - } else { - LOG(ERROR) << "Fail to serialize a protobuf to a string."; - return false; - } +bool DBusUpdateEngineClient::AttemptInstall(const string& omaha_url, + const vector<string>& dlc_ids) { + return proxy_->AttemptInstall(omaha_url, dlc_ids, nullptr); } -bool DBusUpdateEngineClient::GetStatus(int64_t* out_last_checked_time, - double* out_progress, - UpdateStatus* out_update_status, - string* out_new_version, - int64_t* out_new_size) const { - string status_as_string; - const bool success = proxy_->GetStatus(out_last_checked_time, - out_progress, - &status_as_string, - out_new_version, - out_new_size, - nullptr); - if (!success) { +bool DBusUpdateEngineClient::SetDlcActiveValue(bool is_active, + const std::string& dlc_id) { + return proxy_->SetDlcActiveValue(is_active, dlc_id, /*error=*/nullptr); +} + +bool DBusUpdateEngineClient::GetStatus(UpdateEngineStatus* out_status) const { + StatusResult status; + if (!proxy_->GetStatusAdvanced(&status, nullptr)) { return false; } - return StringToUpdateStatus(status_as_string, out_update_status); + ConvertToUpdateEngineStatus(status, out_status); + return true; } bool DBusUpdateEngineClient::SetCohortHint(const string& cohort_hint) { @@ -160,40 +171,25 @@ void DBusUpdateEngineClient::DBusStatusHandlersRegistered( void DBusUpdateEngineClient::StatusUpdateHandlersRegistered( StatusUpdateHandler* handler) const { - int64_t last_checked_time; - double progress; - UpdateStatus update_status; - string new_version; - int64_t new_size; - - if (!GetStatus(&last_checked_time, - &progress, - &update_status, - &new_version, - &new_size)) { + UpdateEngineStatus status; + if (!GetStatus(&status)) { handler->IPCError("Could not query current status"); return; } std::vector<update_engine::StatusUpdateHandler*> just_handler = {handler}; for (auto h : handler ? just_handler : handlers_) { - h->HandleStatusUpdate( - last_checked_time, progress, update_status, new_version, new_size); + h->HandleStatusUpdate(status); } } void DBusUpdateEngineClient::RunStatusUpdateHandlers( - int64_t last_checked_time, - double progress, - const string& current_operation, - const string& new_version, - int64_t new_size) { - UpdateStatus status; - StringToUpdateStatus(current_operation, &status); + const StatusResult& status) { + UpdateEngineStatus ue_status; + ConvertToUpdateEngineStatus(status, &ue_status); for (auto handler : handlers_) { - handler->HandleStatusUpdate( - last_checked_time, progress, status, new_version, new_size); + handler->HandleStatusUpdate(ue_status); } } @@ -210,7 +206,7 @@ bool DBusUpdateEngineClient::UnregisterStatusUpdateHandler( bool DBusUpdateEngineClient::RegisterStatusUpdateHandler( StatusUpdateHandler* handler) { - if (!base::MessageLoopForIO::current()) { + if (!base::MessageLoopCurrent::IsSet()) { LOG(FATAL) << "Cannot get UpdateEngineClient outside of message loop."; return false; } @@ -222,7 +218,7 @@ bool DBusUpdateEngineClient::RegisterStatusUpdateHandler( return true; } - proxy_->RegisterStatusUpdateSignalHandler( + proxy_->RegisterStatusUpdateAdvancedSignalHandler( base::Bind(&DBusUpdateEngineClient::RunStatusUpdateHandlers, base::Unretained(this)), base::Bind(&DBusUpdateEngineClient::DBusStatusHandlersRegistered, @@ -255,9 +251,5 @@ bool DBusUpdateEngineClient::GetLastAttemptError( return proxy_->GetLastAttemptError(last_attempt_error, nullptr); } -bool DBusUpdateEngineClient::GetEolStatus(int32_t* eol_status) const { - return proxy_->GetEolStatus(eol_status, nullptr); -} - } // namespace internal } // namespace update_engine diff --git a/client_library/client_dbus.h b/client_library/client_dbus.h index a186d45d..f19555fc 100644 --- a/client_library/client_dbus.h +++ b/client_library/client_dbus.h @@ -23,6 +23,7 @@ #include <vector> #include <base/macros.h> +#include <update_engine/proto_bindings/update_engine.pb.h> #include "update_engine/client_library/include/update_engine/client.h" #include "update_engine/dbus-proxies.h" @@ -42,13 +43,11 @@ class DBusUpdateEngineClient : public UpdateEngineClient { bool at_user_request) override; bool AttemptInstall(const std::string& omaha_url, - const std::vector<std::string>& dlc_module_ids) override; + const std::vector<std::string>& dlc_ids) override; - bool GetStatus(int64_t* out_last_checked_time, - double* out_progress, - UpdateStatus* out_update_status, - std::string* out_new_version, - int64_t* out_new_size) const override; + bool SetDlcActiveValue(bool is_active, const std::string& dlc_id) override; + + bool GetStatus(UpdateEngineStatus* out_status) const override; bool SetCohortHint(const std::string& cohort_hint) override; bool GetCohortHint(std::string* cohort_hint) const override; @@ -81,8 +80,6 @@ class DBusUpdateEngineClient : public UpdateEngineClient { bool GetLastAttemptError(int32_t* last_attempt_error) const override; - bool GetEolStatus(int32_t* eol_status) const override; - private: void DBusStatusHandlersRegistered(const std::string& interface, const std::string& signal_name, @@ -93,11 +90,7 @@ class DBusUpdateEngineClient : public UpdateEngineClient { // registered handlers receive the event. void StatusUpdateHandlersRegistered(StatusUpdateHandler* handler) const; - void RunStatusUpdateHandlers(int64_t last_checked_time, - double progress, - const std::string& current_operation, - const std::string& new_version, - int64_t new_size); + void RunStatusUpdateHandlers(const StatusResult& status); std::unique_ptr<org::chromium::UpdateEngineInterfaceProxy> proxy_; std::vector<update_engine::StatusUpdateHandler*> handlers_; diff --git a/client_library/include/update_engine/client.h b/client_library/include/update_engine/client.h index 1bc61116..f7347339 100644 --- a/client_library/include/update_engine/client.h +++ b/client_library/include/update_engine/client.h @@ -54,31 +54,18 @@ class UpdateEngineClient { // empty indicates update_engine should use its default value. Note that // update_engine will ignore this parameter in production mode to avoid // pulling untrusted updates. - // |dlc_module_ids| + // |dlc_ids| // A list of DLC module IDs. - virtual bool AttemptInstall( - const std::string& omaha_url, - const std::vector<std::string>& dlc_module_ids) = 0; - - // Returns the current status of the Update Engine. - // - // |out_last_checked_time| - // the last time the update engine checked for an update in seconds since - // the epoc. - // |out_progress| - // when downloading an update, this is calculated as - // (number of bytes received) / (total bytes). - // |out_update_status| - // See update_status.h. - // |out_new_version| - // string version of the new system image. - // |out_new_size| - // number of bytes in the new system image. - virtual bool GetStatus(int64_t* out_last_checked_time, - double* out_progress, - UpdateStatus* out_update_status, - std::string* out_new_version, - int64_t* out_new_size) const = 0; + virtual bool AttemptInstall(const std::string& omaha_url, + const std::vector<std::string>& dlc_ids) = 0; + + // Same as above but return the entire struct instead. + virtual bool GetStatus(UpdateEngineStatus* out_status) const = 0; + + // Sets the DLC as active or inactive. When set to active, the ping metadata + // for the DLC is updated accordingly. When set to inactive, the metadata + // for the DLC is deleted. + virtual bool SetDlcActiveValue(bool is_active, const std::string& dlc_id) = 0; // Getter and setter for the cohort hint. virtual bool SetCohortHint(const std::string& cohort_hint) = 0; @@ -132,9 +119,6 @@ class UpdateEngineClient { // Get the last UpdateAttempt error code. virtual bool GetLastAttemptError(int32_t* last_attempt_error) const = 0; - // Get the current end-of-life status code. See EolStatus enum for details. - virtual bool GetEolStatus(int32_t* eol_status) const = 0; - protected: // Use CreateInstance(). UpdateEngineClient() = default; diff --git a/client_library/include/update_engine/status_update_handler.h b/client_library/include/update_engine/status_update_handler.h index d2fad34c..238f6bda 100644 --- a/client_library/include/update_engine/status_update_handler.h +++ b/client_library/include/update_engine/status_update_handler.h @@ -14,7 +14,9 @@ // limitations under the License. // +// NOLINTNEXTLINE(whitespace/line_length) #ifndef UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_STATUS_UPDATE_HANDLER_H_ +// NOLINTNEXTLINE(whitespace/line_length) #define UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_STATUS_UPDATE_HANDLER_H_ #include <string> @@ -35,13 +37,10 @@ class StatusUpdateHandler { virtual void IPCError(const std::string& error) = 0; // Runs every time update_engine reports a status change. - virtual void HandleStatusUpdate(int64_t last_checked_time, - double progress, - UpdateStatus current_operation, - const std::string& new_version, - int64_t new_size) = 0; + virtual void HandleStatusUpdate(const UpdateEngineStatus& status) = 0; }; } // namespace update_engine +// NOLINTNEXTLINE(whitespace/line_length) #endif // UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_STATUS_UPDATE_HANDLER_H_ diff --git a/client_library/include/update_engine/update_status.h b/client_library/include/update_engine/update_status.h index 6490e27a..043a36e5 100644 --- a/client_library/include/update_engine/update_status.h +++ b/client_library/include/update_engine/update_status.h @@ -21,12 +21,19 @@ #include <brillo/enum_flags.h> +// NOTE: Keep this file in sync with +// platform2/system_api/dbus/update_engine/update_engine.proto especially: +// - |UpdateStatus| <-> |Operation| +// - |UpdateEngineStatus| <-> |StatusResult| + namespace update_engine { -// ATTENTION: When adding a new enum value here, always append at the end and -// make sure to make proper adjustments in UpdateAttempter:ActionCompleted(). If -// any enum memeber is deprecated, the assigned value of other members should -// not change. See b/62842358. +// ATTENTION: +// When adding a new enum value: +// - always append at the end with proper adjustments in |ActionCompleted()|. +// - always update |kNonIdleUpdateStatues| in update_attempter_unittest.cc. +// When deprecating an old enum value: +// - other enum values should not change their old values. See b/62842358. enum class UpdateStatus { IDLE = 0, CHECKING_FOR_UPDATE = 1, @@ -42,6 +49,13 @@ enum class UpdateStatus { // 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 + // Chrome code and evantually from here. This is not really an operation or + // state that the update_engine stays on. This is the result of an internal + // failure and should be reflected differently. + // ERROR = -1, }; // Enum of bit-wise flags for controlling how updates are attempted. @@ -58,23 +72,27 @@ enum UpdateAttemptFlags : int32_t { DECLARE_FLAGS_ENUM(UpdateAttemptFlags); struct UpdateEngineStatus { - // When the update_engine last checked for updates (time_t: seconds from unix - // epoch) + // Update engine last checked update (time_t: seconds from unix epoch). int64_t last_checked_time; - // the current status/operation of the update_engine + // Current status/operation of the update_engine. UpdateStatus status; - // the current product version (oem bundle id) + // Current product version (oem bundle id). std::string current_version; - // the current system version - std::string current_system_version; - // The current progress (0.0f-1.0f). + // Current progress (0.0f-1.0f). double progress; - // the size of the update (bytes) + // Size of the update in bytes. uint64_t new_size_bytes; - // the new product version + // New product version. std::string new_version; - // the new system version, if there is one (empty, otherwise) - std::string new_system_version; + // Wether the update is an enterprise rollback. The value is valid only if the + // current operation is passed CHECKING_FOR_UPDATE. + bool is_enterprise_rollback; + // Indication of install for DLC(s). + bool is_install; + // The end-of-life date of the device in the number of days since Unix Epoch. + int64_t eol_date; + // The system will powerwash once the update is applied. + bool will_powerwash_after_reboot; }; } // namespace update_engine 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 3906e2f5..321174eb 100644 --- a/common/boot_control_interface.h +++ b/common/boot_control_interface.h @@ -59,12 +59,27 @@ class BootControlInterface { // every slot. In order to access the dynamic partitions in the target slot, // 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. On success, - // returns true and stores the block device in |device|. + // 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; + virtual std::optional<PartitionDevice> GetPartitionDevice( + const std::string& partition_name, + uint32_t slot, + uint32_t current_slot, + bool not_in_payload = false) const = 0; // Returns whether the passed |slot| is marked as bootable. Returns false if // the slot is invalid. virtual bool IsSlotBootable(Slot slot) const = 0; @@ -88,7 +103,7 @@ class BootControlInterface { // Check if |slot| is marked boot successfully. virtual bool IsSlotMarkedSuccessful(Slot slot) const = 0; - // Return the dynamic partition control interface. + // Return the dynamic partition control interface. Never null. virtual DynamicPartitionControlInterface* GetDynamicPartitionControl() = 0; // Return a human-readable slot name used for logging. diff --git a/common/boot_control_stub.cc b/common/boot_control_stub.cc index 2eb92116..a1cc0555 100644 --- a/common/boot_control_stub.cc +++ b/common/boot_control_stub.cc @@ -35,6 +35,24 @@ 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; +} + +std::optional<PartitionDevice> BootControlStub::GetPartitionDevice( + const std::string& partition_name, + uint32_t slot, + uint32_t current_slot, + bool not_in_payload) const { + LOG(ERROR) << __FUNCTION__ << " should never be called."; + return {}; +} + bool BootControlStub::GetPartitionDevice(const string& partition_name, Slot slot, string* device) const { diff --git a/common/boot_control_stub.h b/common/boot_control_stub.h index cc161902..dcddbaea 100644 --- a/common/boot_control_stub.h +++ b/common/boot_control_stub.h @@ -41,8 +41,18 @@ class BootControlStub : public BootControlInterface { 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; + std::optional<PartitionDevice> GetPartitionDevice( + const std::string& partition_name, + uint32_t slot, + uint32_t current_slot, + bool not_in_payload = false) const override; bool IsSlotBootable(BootControlInterface::Slot slot) const override; bool MarkSlotUnbootable(BootControlInterface::Slot slot) override; bool SetActiveBootSlot(BootControlInterface::Slot slot) override; diff --git a/common/cleanup_previous_update_action_delegate.h b/common/cleanup_previous_update_action_delegate.h index 7dad9c50..8daf860e 100644 --- a/common/cleanup_previous_update_action_delegate.h +++ b/common/cleanup_previous_update_action_delegate.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_DELEGETE_H_ -#define UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_DELEGETE_H_ +#ifndef UPDATE_ENGINE_COMMON_CLEANUP_PREVIOUS_UPDATE_ACTION_DELEGETE_H_ +#define UPDATE_ENGINE_COMMON_CLEANUP_PREVIOUS_UPDATE_ACTION_DELEGETE_H_ namespace chromeos_update_engine { @@ -29,4 +29,4 @@ class CleanupPreviousUpdateActionDelegateInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_CLEANUP_PREVIOUS_UPDATE_ACTION_DELEGETE_H_ +#endif // UPDATE_ENGINE_COMMON_CLEANUP_PREVIOUS_UPDATE_ACTION_DELEGETE_H_ diff --git a/common/clock.cc b/common/clock.cc index 05c495c3..0821a56f 100644 --- a/common/clock.cc +++ b/common/clock.cc @@ -20,11 +20,11 @@ namespace chromeos_update_engine { -base::Time Clock::GetWallclockTime() { +base::Time Clock::GetWallclockTime() const { return base::Time::Now(); } -base::Time Clock::GetMonotonicTime() { +base::Time Clock::GetMonotonicTime() const { struct timespec now_ts; if (clock_gettime(CLOCK_MONOTONIC_RAW, &now_ts) != 0) { // Avoid logging this as an error as call-sites may call this very @@ -40,7 +40,7 @@ base::Time Clock::GetMonotonicTime() { return base::Time::FromTimeVal(now_tv); } -base::Time Clock::GetBootTime() { +base::Time Clock::GetBootTime() const { struct timespec now_ts; if (clock_gettime(CLOCK_BOOTTIME, &now_ts) != 0) { // Avoid logging this as an error as call-sites may call this very diff --git a/common/clock.h b/common/clock.h index 2f373a72..4021fa11 100644 --- a/common/clock.h +++ b/common/clock.h @@ -24,13 +24,11 @@ namespace chromeos_update_engine { // Implements a clock. class Clock : public ClockInterface { public: - Clock() {} + Clock() = default; - base::Time GetWallclockTime() override; - - base::Time GetMonotonicTime() override; - - base::Time GetBootTime() override; + base::Time GetWallclockTime() const override; + base::Time GetMonotonicTime() const override; + base::Time GetBootTime() const override; private: DISALLOW_COPY_AND_ASSIGN(Clock); diff --git a/common/clock_interface.h b/common/clock_interface.h index 2228983d..176505dc 100644 --- a/common/clock_interface.h +++ b/common/clock_interface.h @@ -32,21 +32,21 @@ class ClockInterface { virtual ~ClockInterface() = default; // Gets the current time e.g. similar to base::Time::Now(). - virtual base::Time GetWallclockTime() = 0; + virtual base::Time GetWallclockTime() const = 0; // Returns monotonic time since some unspecified starting point. It // is not increased when the system is sleeping nor is it affected // by NTP or the user changing the time. // // (This is a simple wrapper around clock_gettime(2) / CLOCK_MONOTONIC_RAW.) - virtual base::Time GetMonotonicTime() = 0; + virtual base::Time GetMonotonicTime() const = 0; // Returns monotonic time since some unspecified starting point. It // is increased when the system is sleeping but it's not affected // by NTP or the user changing the time. // // (This is a simple wrapper around clock_gettime(2) / CLOCK_BOOTTIME.) - virtual base::Time GetBootTime() = 0; + virtual base::Time GetBootTime() const = 0; }; } // namespace chromeos_update_engine diff --git a/connection_utils.cc b/common/connection_utils.cc index aeb01634..44e51286 100644 --- a/connection_utils.cc +++ b/common/connection_utils.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/connection_utils.h" +#include "update_engine/common/connection_utils.h" #include <shill/dbus-constants.h> @@ -32,10 +32,6 @@ ConnectionType ParseConnectionType(const std::string& type_str) { return ConnectionType::kEthernet; } else if (type_str == shill::kTypeWifi) { return ConnectionType::kWifi; - } else if (type_str == shill::kTypeWimax) { - return ConnectionType::kWimax; - } else if (type_str == shill::kTypeBluetooth) { - return ConnectionType::kBluetooth; } else if (type_str == shill::kTypeCellular) { return ConnectionType::kCellular; } else if (type_str == kTypeDisconnected) { @@ -61,10 +57,6 @@ const char* StringForConnectionType(ConnectionType type) { return shill::kTypeEthernet; case ConnectionType::kWifi: return shill::kTypeWifi; - case ConnectionType::kWimax: - return shill::kTypeWimax; - case ConnectionType::kBluetooth: - return shill::kTypeBluetooth; case ConnectionType::kCellular: return shill::kTypeCellular; case ConnectionType::kDisconnected: diff --git a/connection_utils.h b/common/connection_utils.h index d5133a14..5d63fb22 100644 --- a/connection_utils.h +++ b/common/connection_utils.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_CONNECTION_UTILS_H_ -#define UPDATE_ENGINE_CONNECTION_UTILS_H_ +#ifndef UPDATE_ENGINE_COMMON_CONNECTION_UTILS_H_ +#define UPDATE_ENGINE_COMMON_CONNECTION_UTILS_H_ #include <string> @@ -25,8 +25,6 @@ enum class ConnectionType { kDisconnected, kEthernet, kWifi, - kWimax, - kBluetooth, kCellular, kUnknown }; @@ -49,4 +47,4 @@ const char* StringForConnectionType(ConnectionType type); } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_CONNECTION_UTILS_H_ +#endif // UPDATE_ENGINE_COMMON_CONNECTION_UTILS_H_ diff --git a/common/constants.cc b/common/constants.cc index 5bfb2b6f..0677e663 100644 --- a/common/constants.cc +++ b/common/constants.cc @@ -18,6 +18,10 @@ namespace chromeos_update_engine { +const char kExclusionPrefsSubDir[] = "exclusion"; + +const char kDlcPrefsSubDir[] = "dlc"; + const char kPowerwashSafePrefsSubDirectory[] = "update_engine/prefs"; const char kPrefsSubDirectory[] = "prefs"; @@ -55,11 +59,19 @@ const char kPrefsNumResponsesSeen[] = "num-responses-seen"; const char kPrefsOmahaCohort[] = "omaha-cohort"; const char kPrefsOmahaCohortHint[] = "omaha-cohort-hint"; const char kPrefsOmahaCohortName[] = "omaha-cohort-name"; -const char kPrefsOmahaEolStatus[] = "omaha-eol-status"; +const char kPrefsOmahaEolDate[] = "omaha-eol-date"; const char kPrefsP2PEnabled[] = "p2p-enabled"; const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp"; const char kPrefsP2PNumAttempts[] = "p2p-num-attempts"; const char kPrefsPayloadAttemptNumber[] = "payload-attempt-number"; +const char kPrefsTestUpdateCheckIntervalTimeout[] = + "test-update-check-interval-timeout"; +// Keep |kPrefsPingActive| in sync with |kDlcMetadataFilePingActive| in +// dlcservice. +const char kPrefsPingActive[] = "active"; +const char kPrefsPingLastActive[] = "date_last_active"; +const char kPrefsPingLastRollcall[] = "date_last_rollcall"; +const char kPrefsLastFp[] = "last-fp"; const char kPrefsPostInstallSucceeded[] = "post-install-succeeded"; const char kPrefsPreviousVersion[] = "previous-version"; const char kPrefsResumedUpdateFailures[] = "resumed-update-failures"; @@ -99,6 +111,8 @@ 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"; +const char kPrefsPreviousSlot[] = "previous-slot"; // These four fields are generated by scripts/brillo_update_payload. const char kPayloadPropertyFileSize[] = "FILE_SIZE"; @@ -123,4 +137,12 @@ const char kPayloadPropertySwitchSlotOnReboot[] = "SWITCH_SLOT_ON_REBOOT"; // The default is 1 (always run post install). const char kPayloadPropertyRunPostInstall[] = "RUN_POST_INSTALL"; +const char kOmahaUpdaterVersion[] = "0.1.0.0"; + +// X-Goog-Update headers. +const char kXGoogleUpdateInteractivity[] = "X-Goog-Update-Interactivity"; +const char kXGoogleUpdateAppId[] = "X-Goog-Update-AppId"; +const char kXGoogleUpdateUpdater[] = "X-Goog-Update-Updater"; +const char kXGoogleUpdateSessionId[] = "X-Goog-SessionId"; + } // namespace chromeos_update_engine diff --git a/common/constants.h b/common/constants.h index af1c0ab6..68f720db 100644 --- a/common/constants.h +++ b/common/constants.h @@ -17,8 +17,16 @@ #ifndef UPDATE_ENGINE_COMMON_CONSTANTS_H_ #define UPDATE_ENGINE_COMMON_CONSTANTS_H_ +#include <cstdint> + namespace chromeos_update_engine { +// The root path of all exclusion prefs. +extern const char kExclusionPrefsSubDir[]; + +// The root path of all DLC metadata. +extern const char kDlcPrefsSubDir[]; + // Directory for AU prefs that are preserved across powerwash. extern const char kPowerwashSafePrefsSubDirectory[]; @@ -56,13 +64,19 @@ extern const char kPrefsNumResponsesSeen[]; extern const char kPrefsOmahaCohort[]; extern const char kPrefsOmahaCohortHint[]; extern const char kPrefsOmahaCohortName[]; -extern const char kPrefsOmahaEolStatus[]; +extern const char kPrefsOmahaEolDate[]; extern const char kPrefsP2PEnabled[]; extern const char kPrefsP2PFirstAttemptTimestamp[]; extern const char kPrefsP2PNumAttempts[]; extern const char kPrefsPayloadAttemptNumber[]; +extern const char kPrefsTestUpdateCheckIntervalTimeout[]; +extern const char kPrefsPingActive[]; +extern const char kPrefsPingLastActive[]; +extern const char kPrefsPingLastRollcall[]; +extern const char kPrefsLastFp[]; extern const char kPrefsPostInstallSucceeded[]; extern const char kPrefsPreviousVersion[]; +extern const char kPrefsPreviousSlot[]; extern const char kPrefsResumedUpdateFailures[]; extern const char kPrefsRollbackHappened[]; extern const char kPrefsRollbackVersion[]; @@ -95,6 +109,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[]; @@ -108,6 +123,14 @@ extern const char kPayloadPropertyNetworkId[]; extern const char kPayloadPropertySwitchSlotOnReboot[]; extern const char kPayloadPropertyRunPostInstall[]; +extern const char kOmahaUpdaterVersion[]; + +// X-Goog-Update headers. +extern const char kXGoogleUpdateInteractivity[]; +extern const char kXGoogleUpdateAppId[]; +extern const char kXGoogleUpdateUpdater[]; +extern const char kXGoogleUpdateSessionId[]; + // A download source is any combination of protocol and server (that's of // interest to us when looking at UMA metrics) using which we may download // the payload. @@ -134,30 +157,30 @@ typedef enum { } PayloadType; // Maximum number of times we'll allow using p2p for the same update payload. -const int kMaxP2PAttempts = 10; +constexpr int kMaxP2PAttempts = 10; // Maximum wallclock time we allow attempting to update using p2p for // the same update payload - five days. -const int kMaxP2PAttemptTimeSeconds = 5 * 24 * 60 * 60; +constexpr int kMaxP2PAttemptTimeSeconds = 5 * 24 * 60 * 60; // The maximum amount of time to spend waiting for p2p-client(1) to // return while waiting in line to use the LAN - six hours. -const int kMaxP2PNetworkWaitTimeSeconds = 6 * 60 * 60; +constexpr int kMaxP2PNetworkWaitTimeSeconds = 6 * 60 * 60; // The maximum number of payload files to keep in /var/cache/p2p. -const int kMaxP2PFilesToKeep = 3; +constexpr int kMaxP2PFilesToKeep = 3; // The maximum number of days to keep a p2p file; -const int kMaxP2PFileAgeDays = 5; +constexpr int kMaxP2PFileAgeDays = 5; // The default number of UMA buckets for metrics. -const int kNumDefaultUmaBuckets = 50; +constexpr int kNumDefaultUmaBuckets = 50; -// General constants -const int kNumBytesInOneMiB = 1024 * 1024; +// General constexprants +constexpr int kNumBytesInOneMiB = 1024 * 1024; // Number of redirects allowed when downloading. -const int kDownloadMaxRedirects = 10; +constexpr int kDownloadMaxRedirects = 10; // The minimum average speed that downloads must sustain... // @@ -165,8 +188,8 @@ const int kDownloadMaxRedirects = 10; // connectivity and we want to make as much forward progress as // possible. For p2p this is high (25 kB/second) since we can assume // high bandwidth (same LAN) and we want to fail fast. -const int kDownloadLowSpeedLimitBps = 1; -const int kDownloadP2PLowSpeedLimitBps = 25 * 1000; +constexpr int kDownloadLowSpeedLimitBps = 1; +constexpr int kDownloadP2PLowSpeedLimitBps = 25 * 1000; // ... measured over this period. // @@ -175,18 +198,18 @@ const int kDownloadP2PLowSpeedLimitBps = 25 * 1000; // for the workstation to generate the payload. For normal operation // and p2p, make this relatively low since we want to fail fast in // those cases. -const int kDownloadLowSpeedTimeSeconds = 30; -const int kDownloadDevModeLowSpeedTimeSeconds = 180; -const int kDownloadP2PLowSpeedTimeSeconds = 60; +constexpr int kDownloadLowSpeedTimeSeconds = 30; +constexpr int kDownloadDevModeLowSpeedTimeSeconds = 180; +constexpr int kDownloadP2PLowSpeedTimeSeconds = 60; // The maximum amount of HTTP server reconnect attempts. // // This is set high in order to maximize the attempt's chance of // succeeding. When using p2p, this is low in order to fail fast. -const int kDownloadMaxRetryCount = 20; -const int kDownloadMaxRetryCountOobeNotComplete = 3; -const int kDownloadMaxRetryCountInteractive = 3; -const int kDownloadP2PMaxRetryCount = 5; +constexpr int kDownloadMaxRetryCount = 20; +constexpr int kDownloadMaxRetryCountOobeNotComplete = 3; +constexpr int kDownloadMaxRetryCountInteractive = 3; +constexpr int kDownloadP2PMaxRetryCount = 5; // The connect timeout, in seconds. // @@ -194,11 +217,19 @@ const int kDownloadP2PMaxRetryCount = 5; // connectivity and we may be using HTTPS which involves complicated // multi-roundtrip setup. For p2p, this is set low because we can // the server is on the same LAN and we want to fail fast. -const int kDownloadConnectTimeoutSeconds = 30; -const int kDownloadP2PConnectTimeoutSeconds = 5; +constexpr int kDownloadConnectTimeoutSeconds = 30; +constexpr int kDownloadP2PConnectTimeoutSeconds = 5; // Size in bytes of SHA256 hash. -const int kSHA256Size = 32; +constexpr int kSHA256Size = 32; + +// A hardcoded label to mark end of all InstallOps +// This number must be greater than number of install ops. +// Number of install ops is bounded by number of blocks on any partition. +// Currently, the block size is 4096. Using |kEndOfInstallLabel| of 2^48 will +// allow partitions with 2^48 * 4096 = 2^60 bytes. That's 1024PB? Partitions on +// android aren't getting that big any time soon. +constexpr uint64_t kEndOfInstallLabel = (1ULL << 48); } // namespace chromeos_update_engine diff --git a/common/cow_operation_convert.cc b/common/cow_operation_convert.cc new file mode 100644 index 00000000..2564abf0 --- /dev/null +++ b/common/cow_operation_convert.cc @@ -0,0 +1,82 @@ +// +// 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/common/cow_operation_convert.h" + +#include <base/logging.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 { + +std::vector<CowOperation> ConvertToCowOperations( + const ::google::protobuf::RepeatedPtrField< + ::chromeos_update_engine::InstallOperation>& operations, + const ::google::protobuf::RepeatedPtrField<CowMergeOperation>& + merge_operations) { + ExtentRanges merge_extents; + std::vector<CowOperation> converted; + ExtentRanges modified_extents; + + // We want all CowCopy ops to be done first, before any COW_REPLACE happen. + // Therefore we add these ops in 2 separate loops. This is because during + // merge, a CowReplace might modify a block needed by CowCopy, so we always + // perform CowCopy first. + + // This loop handles CowCopy blocks within SOURCE_COPY, and the next loop + // converts the leftover blocks to CowReplace? + for (const auto& merge_op : merge_operations) { + if (merge_op.type() != CowMergeOperation::COW_COPY) { + continue; + } + merge_extents.AddExtent(merge_op.dst_extent()); + const auto& src_extent = merge_op.src_extent(); + const auto& dst_extent = merge_op.dst_extent(); + // Add blocks in reverse order, because snapused specifically prefers this + // ordering. Since we already eliminated all self-overlapping SOURCE_COPY + // during delta generation, this should be safe to do. + for (uint64_t i = src_extent.num_blocks(); i > 0; i--) { + auto src_block = src_extent.start_block() + i - 1; + auto dst_block = dst_extent.start_block() + i - 1; + converted.push_back({CowOperation::CowCopy, src_block, dst_block}); + modified_extents.AddBlock(dst_block); + } + } + // COW_REPLACE are added after COW_COPY, because replace might modify blocks + // needed by COW_COPY. Please don't merge this loop with the previous one. + for (const auto& operation : operations) { + if (operation.type() != InstallOperation::SOURCE_COPY) { + continue; + } + const auto& src_extents = operation.src_extents(); + const auto& dst_extents = operation.dst_extents(); + BlockIterator it1{src_extents}; + BlockIterator it2{dst_extents}; + while (!it1.is_end() && !it2.is_end()) { + auto src_block = *it1; + auto dst_block = *it2; + if (!merge_extents.ContainsBlock(dst_block)) { + converted.push_back({CowOperation::CowReplace, src_block, dst_block}); + } + ++it1; + ++it2; + } + } + return converted; +} +} // namespace chromeos_update_engine diff --git a/common/cow_operation_convert.h b/common/cow_operation_convert.h new file mode 100644 index 00000000..c0543f7b --- /dev/null +++ b/common/cow_operation_convert.h @@ -0,0 +1,56 @@ +// +// 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 __COW_OPERATION_CONVERT_H +#define __COW_OPERATION_CONVERT_H + +#include <vector> + +#include <libsnapshot/cow_format.h> + +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { +struct CowOperation { + enum Type { + CowCopy = android::snapshot::kCowCopyOp, + CowReplace = android::snapshot::kCowReplaceOp, + }; + Type op; + uint64_t src_block; + uint64_t dst_block; +}; + +// Convert SOURCE_COPY operations in `operations` list to a list of +// CowOperations according to the merge sequence. This function only converts +// SOURCE_COPY, other operations are ignored. If there's a merge conflict in +// SOURCE_COPY operations, some blocks may be converted to COW_REPLACE instead +// of COW_COPY. + +// The list returned does not necessarily preserve the order of +// SOURCE_COPY in `operations`. The only guarantee about ordering in the +// returned list is that if operations are applied in such order, there would be +// no merge conflicts. + +// This funnction is intended to be used by delta_performer to perform +// SOURCE_COPY operations on Virtual AB Compression devices. +std::vector<CowOperation> ConvertToCowOperations( + const ::google::protobuf::RepeatedPtrField< + ::chromeos_update_engine::InstallOperation>& operations, + const ::google::protobuf::RepeatedPtrField<CowMergeOperation>& + merge_operations); +} // namespace chromeos_update_engine +#endif diff --git a/common/cow_operation_convert_unittest.cc b/common/cow_operation_convert_unittest.cc new file mode 100644 index 00000000..93173fe8 --- /dev/null +++ b/common/cow_operation_convert_unittest.cc @@ -0,0 +1,236 @@ +// +// 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 <array> +#include <initializer_list> + +#include <gtest/gtest.h> + +#include "update_engine/common/cow_operation_convert.h" +#include "update_engine/payload_generator/extent_ranges.h" +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { +using OperationList = ::google::protobuf::RepeatedPtrField< + ::chromeos_update_engine::InstallOperation>; +using MergeOplist = ::google::protobuf::RepeatedPtrField< + ::chromeos_update_engine::CowMergeOperation>; + +std::ostream& operator<<(std::ostream& out, CowOperation::Type op) { + switch (op) { + case CowOperation::Type::CowCopy: + out << "CowCopy"; + break; + case CowOperation::Type::CowReplace: + out << "CowReplace"; + break; + default: + out << op; + break; + } + return out; +} + +std::ostream& operator<<(std::ostream& out, const CowOperation& c) { + out << "{" << c.op << ", " << c.src_block << ", " << c.dst_block << "}"; + return out; +} + +class CowOperationConvertTest : public testing::Test { + public: + void VerifyCowMergeOp(const std::vector<CowOperation>& cow_ops) { + // Build a set of all extents covered by InstallOps. + ExtentRanges src_extent_set; + ExtentRanges dst_extent_set; + for (auto&& op : operations_) { + src_extent_set.AddRepeatedExtents(op.src_extents()); + dst_extent_set.AddRepeatedExtents(op.dst_extents()); + } + ExtentRanges modified_extents; + for (auto&& cow_op : cow_ops) { + if (cow_op.op == CowOperation::CowCopy) { + EXPECT_TRUE(src_extent_set.ContainsBlock(cow_op.src_block)); + // converted operations should be conflict free. + EXPECT_FALSE(modified_extents.ContainsBlock(cow_op.src_block)) + << "SOURCE_COPY operation " << cow_op + << " read from a modified block"; + } + EXPECT_TRUE(dst_extent_set.ContainsBlock(cow_op.dst_block)); + dst_extent_set.SubtractExtent(ExtentForRange(cow_op.dst_block, 1)); + modified_extents.AddBlock(cow_op.dst_block); + } + // The generated CowOps should cover all extents in InstallOps. + EXPECT_EQ(dst_extent_set.blocks(), 0UL); + // It's possible that src_extent_set is non-empty, because some operations + // will be converted to CowReplace, and we don't count the source extent for + // those. + } + OperationList operations_; + MergeOplist merge_operations_; +}; + +void AddOperation(OperationList* operations, + ::chromeos_update_engine::InstallOperation_Type op_type, + std::initializer_list<std::array<int, 2>> src_extents, + std::initializer_list<std::array<int, 2>> dst_extents) { + auto&& op = operations->Add(); + op->set_type(op_type); + for (const auto& extent : src_extents) { + *op->add_src_extents() = ExtentForRange(extent[0], extent[1]); + } + for (const auto& extent : dst_extents) { + *op->add_dst_extents() = ExtentForRange(extent[0], extent[1]); + } +} + +void AddMergeOperation(MergeOplist* operations, + ::chromeos_update_engine::CowMergeOperation_Type op_type, + std::array<int, 2> src_extent, + std::array<int, 2> dst_extent) { + auto&& op = operations->Add(); + op->set_type(op_type); + *op->mutable_src_extent() = ExtentForRange(src_extent[0], src_extent[1]); + *op->mutable_dst_extent() = ExtentForRange(dst_extent[0], dst_extent[1]); +} + +TEST_F(CowOperationConvertTest, NoConflict) { + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{20, 1}}, {{30, 1}}); + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{10, 1}}, {{20, 1}}); + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{0, 1}}, {{10, 1}}); + + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {20, 1}, {30, 1}); + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {10, 1}, {20, 1}); + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {0, 1}, {10, 1}); + + auto cow_ops = ConvertToCowOperations(operations_, merge_operations_); + ASSERT_EQ(cow_ops.size(), 3UL); + ASSERT_TRUE(std::all_of(cow_ops.begin(), cow_ops.end(), [](auto&& cow_op) { + return cow_op.op == CowOperation::CowCopy; + })); + VerifyCowMergeOp(cow_ops); +} + +TEST_F(CowOperationConvertTest, CowReplace) { + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{30, 1}}, {{0, 1}}); + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{20, 1}}, {{30, 1}}); + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{10, 1}}, {{20, 1}}); + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{0, 1}}, {{10, 1}}); + + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {20, 1}, {30, 1}); + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {10, 1}, {20, 1}); + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {0, 1}, {10, 1}); + + auto cow_ops = ConvertToCowOperations(operations_, merge_operations_); + ASSERT_EQ(cow_ops.size(), 4UL); + // Expect 3 COW_COPY and 1 COW_REPLACE + ASSERT_EQ(std::count_if(cow_ops.begin(), + cow_ops.end(), + [](auto&& cow_op) { + return cow_op.op == CowOperation::CowCopy; + }), + 3); + ASSERT_EQ(std::count_if(cow_ops.begin(), + cow_ops.end(), + [](auto&& cow_op) { + return cow_op.op == CowOperation::CowReplace; + }), + 1); + VerifyCowMergeOp(cow_ops); +} + +TEST_F(CowOperationConvertTest, ReOrderSourceCopy) { + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{30, 1}}, {{20, 1}}); + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{20, 1}}, {{10, 1}}); + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{10, 1}}, {{0, 1}}); + + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {10, 1}, {0, 1}); + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {20, 1}, {10, 1}); + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {30, 1}, {20, 1}); + + auto cow_ops = ConvertToCowOperations(operations_, merge_operations_); + ASSERT_EQ(cow_ops.size(), 3UL); + // Expect 3 COW_COPY + ASSERT_TRUE(std::all_of(cow_ops.begin(), cow_ops.end(), [](auto&& cow_op) { + return cow_op.op == CowOperation::CowCopy; + })); + VerifyCowMergeOp(cow_ops); +} + +TEST_F(CowOperationConvertTest, InterleavingSrcExtent) { + AddOperation(&operations_, + InstallOperation::SOURCE_COPY, + {{30, 5}, {35, 5}}, + {{20, 10}}); + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{20, 1}}, {{10, 1}}); + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{10, 1}}, {{0, 1}}); + + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {10, 1}, {0, 1}); + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {20, 1}, {10, 1}); + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {30, 5}, {20, 5}); + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {35, 5}, {25, 5}); + + auto cow_ops = ConvertToCowOperations(operations_, merge_operations_); + // Expect 4 COW_COPY + ASSERT_EQ(cow_ops.size(), 12UL); + ASSERT_TRUE(std::all_of(cow_ops.begin(), cow_ops.end(), [](auto&& cow_op) { + return cow_op.op == CowOperation::CowCopy; + })); + VerifyCowMergeOp(cow_ops); +} + +TEST_F(CowOperationConvertTest, SelfOverlappingOperation) { + AddOperation( + &operations_, InstallOperation::SOURCE_COPY, {{20, 10}}, {{25, 10}}); + + AddMergeOperation( + &merge_operations_, CowMergeOperation::COW_COPY, {20, 10}, {25, 10}); + + auto cow_ops = ConvertToCowOperations(operations_, merge_operations_); + // Expect 10 COW_COPY + ASSERT_EQ(cow_ops.size(), 10UL); + ASSERT_TRUE(std::all_of(cow_ops.begin(), cow_ops.end(), [](auto&& cow_op) { + return cow_op.op == CowOperation::CowCopy; + })); + VerifyCowMergeOp(cow_ops); +} + +} // namespace chromeos_update_engine diff --git a/common/cpu_limiter.cc b/common/cpu_limiter.cc index 1d14764f..5f1ae6f0 100644 --- a/common/cpu_limiter.cc +++ b/common/cpu_limiter.cc @@ -67,7 +67,7 @@ bool CPULimiter::SetCpuShares(CpuShares shares) { if (shares_ == shares) return true; - std::string string_shares = base::IntToString(static_cast<int>(shares)); + std::string string_shares = base::NumberToString(static_cast<int>(shares)); LOG(INFO) << "Setting cgroup cpu shares to " << string_shares; if (!utils::WriteFile( kCGroupSharesPath, string_shares.c_str(), string_shares.size())) { diff --git a/common/cpu_limiter.h b/common/cpu_limiter.h index c7add89d..e6d7331e 100644 --- a/common/cpu_limiter.h +++ b/common/cpu_limiter.h @@ -30,10 +30,6 @@ enum class CpuShares : int { kLow = 2, }; -// Sets the current process shares to |shares|. Returns true on -// success, false otherwise. -bool SetCpuShares(CpuShares shares); - class CPULimiter { public: CPULimiter() = default; diff --git a/common/daemon_base.h b/common/daemon_base.h new file mode 100644 index 00000000..4bc5ef7d --- /dev/null +++ b/common/daemon_base.h @@ -0,0 +1,40 @@ +// +// 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_DAEMON_BASE_H_ +#define UPDATE_ENGINE_COMMON_DAEMON_BASE_H_ + +#include <memory> + +#include <brillo/daemons/daemon.h> + +namespace chromeos_update_engine { + +class DaemonBase : public brillo::Daemon { + public: + DaemonBase() = default; + virtual ~DaemonBase() = default; + + // Creates an instance of the daemon. + static std::unique_ptr<DaemonBase> CreateInstance(); + + private: + DISALLOW_COPY_AND_ASSIGN(DaemonBase); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_DAEMON_BASE_H_ diff --git a/daemon_state_interface.h b/common/daemon_state_interface.h index 23568168..831e38bc 100644 --- a/daemon_state_interface.h +++ b/common/daemon_state_interface.h @@ -14,12 +14,11 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_DAEMON_STATE_INTERFACE_H_ -#define UPDATE_ENGINE_DAEMON_STATE_INTERFACE_H_ +#ifndef UPDATE_ENGINE_COMMON_DAEMON_STATE_INTERFACE_H_ +#define UPDATE_ENGINE_COMMON_DAEMON_STATE_INTERFACE_H_ -#include "update_engine/service_observer_interface.h" +#include "update_engine/common/service_observer_interface.h" -#include <memory> #include <set> namespace chromeos_update_engine { @@ -42,8 +41,10 @@ class DaemonStateInterface { protected: DaemonStateInterface() = default; + + DISALLOW_COPY_AND_ASSIGN(DaemonStateInterface); }; } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_DAEMON_STATE_INTERFACE_H_ +#endif // UPDATE_ENGINE_COMMON_DAEMON_STATE_INTERFACE_H_ diff --git a/common/dlcservice_interface.h b/common/dlcservice_interface.h index aa241053..7b577104 100644 --- a/common/dlcservice_interface.h +++ b/common/dlcservice_interface.h @@ -17,6 +17,7 @@ #ifndef UPDATE_ENGINE_COMMON_DLCSERVICE_INTERFACE_H_ #define UPDATE_ENGINE_COMMON_DLCSERVICE_INTERFACE_H_ +#include <memory> #include <string> #include <vector> @@ -30,9 +31,17 @@ class DlcServiceInterface { public: virtual ~DlcServiceInterface() = default; - // Returns true and a list of installed DLC module ids in |dlc_module_ids|. + // Returns true and a list of installed DLC ids in |dlc_ids|. // On failure it returns false. - virtual bool GetInstalled(std::vector<std::string>* dlc_module_ids) = 0; + virtual bool GetDlcsToUpdate(std::vector<std::string>* dlc_ids) = 0; + + // Returns true if dlcservice successfully handled the install completion + // method call, otherwise false. + virtual bool InstallCompleted(const std::vector<std::string>& dlc_ids) = 0; + + // Returns true if dlcservice successfully handled the update completion + // method call, otherwise false. + virtual bool UpdateCompleted(const std::vector<std::string>& dlc_ids) = 0; protected: DlcServiceInterface() = default; @@ -41,6 +50,10 @@ class DlcServiceInterface { DISALLOW_COPY_AND_ASSIGN(DlcServiceInterface); }; +// This factory function creates a new DlcServiceInterface instance for the +// current platform. +std::unique_ptr<DlcServiceInterface> CreateDlcService(); + } // namespace chromeos_update_engine #endif // UPDATE_ENGINE_COMMON_DLCSERVICE_INTERFACE_H_ diff --git a/common/dlcservice_stub.cc b/common/dlcservice_stub.cc index c5f9306e..24471470 100644 --- a/common/dlcservice_stub.cc +++ b/common/dlcservice_stub.cc @@ -27,9 +27,16 @@ std::unique_ptr<DlcServiceInterface> CreateDlcService() { return std::make_unique<DlcServiceStub>(); } -bool DlcServiceStub::GetInstalled(std::vector<std::string>* dlc_module_ids) { - if (dlc_module_ids) - dlc_module_ids->clear(); +bool DlcServiceStub::GetDlcsToUpdate(vector<string>* dlc_ids) { + if (dlc_ids) + dlc_ids->clear(); + return true; +} + +bool DlcServiceStub::InstallCompleted(const vector<string>& dlc_ids) { + return true; +} +bool DlcServiceStub::UpdateCompleted(const vector<string>& dlc_ids) { return true; } diff --git a/common/dlcservice_stub.h b/common/dlcservice_stub.h index 4e12c113..bc803e80 100644 --- a/common/dlcservice_stub.h +++ b/common/dlcservice_stub.h @@ -31,7 +31,9 @@ class DlcServiceStub : public DlcServiceInterface { ~DlcServiceStub() = default; // BootControlInterface overrides. - bool GetInstalled(std::vector<std::string>* dlc_module_ids) override; + bool GetDlcsToUpdate(std::vector<std::string>* dlc_ids) override; + bool InstallCompleted(const std::vector<std::string>& dlc_ids) override; + bool UpdateCompleted(const std::vector<std::string>& dlc_ids) override; private: DISALLOW_COPY_AND_ASSIGN(DlcServiceStub); diff --git a/payload_consumer/download_action.h b/common/download_action.h index 1777e22f..7b496b17 100644 --- a/payload_consumer/download_action.h +++ b/common/download_action.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_DOWNLOAD_ACTION_H_ -#define UPDATE_ENGINE_PAYLOAD_CONSUMER_DOWNLOAD_ACTION_H_ +#ifndef UPDATE_ENGINE_COMMON_DOWNLOAD_ACTION_H_ +#define UPDATE_ENGINE_COMMON_DOWNLOAD_ACTION_H_ #include <fcntl.h> #include <sys/stat.h> @@ -23,6 +23,7 @@ #include <memory> #include <string> +#include <utility> #include "update_engine/common/action.h" #include "update_engine/common/boot_control_interface.h" @@ -30,7 +31,6 @@ #include "update_engine/common/multi_range_http_fetcher.h" #include "update_engine/payload_consumer/delta_performer.h" #include "update_engine/payload_consumer/install_plan.h" -#include "update_engine/system_state.h" // The Download Action downloads a specified url to disk. The url should point // to an update in a delta payload format. The payload will be piped into a @@ -71,12 +71,11 @@ class DownloadAction : public InstallPlanAction, public HttpFetcherDelegate { // Takes ownership of the passed in HttpFetcher. Useful for testing. // A good calling pattern is: - // DownloadAction(prefs, boot_contol, hardware, system_state, + // DownloadAction(prefs, boot_contol, hardware, // new WhateverHttpFetcher, false); DownloadAction(PrefsInterface* prefs, BootControlInterface* boot_control, HardwareInterface* hardware, - SystemState* system_state, HttpFetcher* http_fetcher, bool interactive); ~DownloadAction() override; @@ -89,7 +88,9 @@ class DownloadAction : public InstallPlanAction, public HttpFetcherDelegate { std::string Type() const override { return StaticType(); } // Testing - void SetTestFileWriter(FileWriter* writer) { writer_ = writer; } + void SetTestFileWriter(std::unique_ptr<DeltaPerformer> writer) { + delta_performer_ = std::move(writer); + } int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); } @@ -108,46 +109,22 @@ class DownloadAction : public InstallPlanAction, public HttpFetcherDelegate { HttpFetcher* http_fetcher() { return http_fetcher_.get(); } - // Returns the p2p file id for the file being written or the empty - // string if we're not writing to a p2p file. - std::string p2p_file_id() { return p2p_file_id_; } - private: - // Closes the file descriptor for the p2p file being written and - // clears |p2p_file_id_| to indicate that we're no longer sharing - // the file. If |delete_p2p_file| is True, also deletes the file. - // If there is no p2p file descriptor, this method does nothing. - void CloseP2PSharingFd(bool delete_p2p_file); - - // Starts sharing the p2p file. Must be called before - // WriteToP2PFile(). Returns True if this worked. - bool SetupP2PSharingFd(); - - // Writes |length| bytes of payload from |data| into |file_offset| - // of the p2p file. Also does sanity 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}; - // SystemState required pointers. + // Required pointers. PrefsInterface* prefs_; BootControlInterface* boot_control_; HardwareInterface* hardware_; - // Global context for the system. - SystemState* system_state_; - // Pointer to the MultiRangeHttpFetcher that does the http work. std::unique_ptr<MultiRangeHttpFetcher> http_fetcher_; @@ -156,10 +133,6 @@ class DownloadAction : public InstallPlanAction, public HttpFetcherDelegate { // update. bool interactive_; - // The FileWriter that downloaded data should be written to. It will - // either point to *decompressing_file_writer_ or *delta_performer_. - FileWriter* writer_; - std::unique_ptr<DeltaPerformer> delta_performer_; // Used by TransferTerminated to figure if this action terminated itself or @@ -173,17 +146,6 @@ class DownloadAction : public InstallPlanAction, public HttpFetcherDelegate { uint64_t bytes_total_{0}; bool download_active_{false}; - // The file-id for the file we're sharing or the empty string - // if we're not using p2p to share. - std::string p2p_file_id_; - - // The file descriptor for the p2p file used for caching the payload or -1 - // if we're not using p2p to share. - int p2p_sharing_fd_; - - // Set to |false| if p2p file is not visible. - bool p2p_visible_; - // Loaded from prefs before downloading any payload. size_t resume_payload_index_{0}; @@ -199,4 +161,4 @@ static_assert(8 == sizeof(off_t), "off_t not 64 bit"); } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_DOWNLOAD_ACTION_H_ +#endif // UPDATE_ENGINE_COMMON_DOWNLOAD_ACTION_H_ diff --git a/common/dynamic_partition_control_interface.h b/common/dynamic_partition_control_interface.h index 58ebfe46..a5be6e1a 100644 --- a/common/dynamic_partition_control_interface.h +++ b/common/dynamic_partition_control_interface.h @@ -21,14 +21,28 @@ #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/common/prefs_interface.h" +#include "update_engine/payload_consumer/file_descriptor.h" #include "update_engine/update_metadata.pb.h" +// Forware declare for libsnapshot/snapshot_writer.h +namespace android::snapshot { +class ISnapshotWriter; +} + namespace chromeos_update_engine { +struct PartitionDevice { + std::string rw_device_path; + std::string readonly_device_path; + bool is_dynamic; +}; + struct FeatureFlag { enum class Value { NONE = 0, RETROFIT, LAUNCH }; constexpr explicit FeatureFlag(Value value) : value_(value) {} @@ -41,7 +55,6 @@ struct FeatureFlag { }; class BootControlInterface; -class PrefsInterface; class DynamicPartitionControlInterface { public: @@ -55,6 +68,11 @@ class DynamicPartitionControlInterface { // Return the feature flags of Virtual A/B on this device. virtual FeatureFlag GetVirtualAbFeatureFlag() = 0; + // Return the feature flags of Virtual A/B Compression on this device. + // This function will tell you if current device supports VABC. However, it + // DOES NOT tell you if VABC is used for current OTA update. For that, use + // UpdateUsesSnapshotCompression. + virtual FeatureFlag GetVirtualAbCompressionFeatureFlag() = 0; // Attempt to optimize |operation|. // If successful, |optimized| contains an operation with extents that @@ -118,6 +136,59 @@ class DynamicPartitionControlInterface { // progress, while ResetUpdate() forcefully free previously // allocated space for snapshot updates. virtual bool ResetUpdate(PrefsInterface* prefs) = 0; + + // Reads the dynamic partitions metadata from the given slot, and puts the + // name of the dynamic partitions with the current suffix to |partitions|. + // Returns true on success. + virtual bool ListDynamicPartitionsForSlot( + uint32_t slot, + 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; + // Partition name is expected to be unsuffixed. e.g. system, vendor + // Return an interface to write to a snapshoted partition. + // If `is_append` is false, then existing COW data will be overwritten. + // Otherwise the cow writer will be opened on APPEND mode, existing COW data + // is preserved. + virtual std::unique_ptr<android::snapshot::ISnapshotWriter> OpenCowWriter( + const std::string& unsuffixed_partition_name, + const std::optional<std::string>&, + bool is_append = false) = 0; + // Open a general purpose FD capable to reading and writing to COW. Note that + // writes must be block aligned. + virtual FileDescriptorPtr OpenCowFd( + const std::string& unsuffixed_partition_name, + const std::optional<std::string>&, + bool is_append = false) = 0; + + virtual bool IsDynamicPartition(const std::string& part_name, + uint32_t slot) = 0; + + // Create virtual block devices for all partitions. + virtual bool MapAllPartitions() = 0; + // Unmap virtual block devices for all partitions. + virtual bool UnmapAllPartitions() = 0; + + // Return if snapshot compression is enabled for this update. + // This function should only be called after preparing for an update + // (PreparePartitionsForUpdate), and before merging + // (see GetCleanupPreviousUpdateAction and CleanupPreviousUpdateAction) or + // resetting it (ResetUpdate). + // + // To know if the device supports snapshot compression by itself, use + // GetVirtualAbCompressionFeatureFlag + virtual bool UpdateUsesSnapshotCompression() = 0; }; } // namespace chromeos_update_engine diff --git a/common/dynamic_partition_control_stub.cc b/common/dynamic_partition_control_stub.cc index 903b7ee0..dd30a8b6 100644 --- a/common/dynamic_partition_control_stub.cc +++ b/common/dynamic_partition_control_stub.cc @@ -20,6 +20,7 @@ #include <string> #include <base/logging.h> +#include <libsnapshot/cow_writer.h> #include "update_engine/common/dynamic_partition_control_stub.h" @@ -33,6 +34,10 @@ FeatureFlag DynamicPartitionControlStub::GetVirtualAbFeatureFlag() { return FeatureFlag(FeatureFlag::Value::NONE); } +FeatureFlag DynamicPartitionControlStub::GetVirtualAbCompressionFeatureFlag() { + return FeatureFlag(FeatureFlag::Value::NONE); +} + bool DynamicPartitionControlStub::OptimizeOperation( const std::string& partition_name, const InstallOperation& operation, @@ -67,4 +72,47 @@ bool DynamicPartitionControlStub::ResetUpdate(PrefsInterface* prefs) { return false; } +bool DynamicPartitionControlStub::ListDynamicPartitionsForSlot( + uint32_t slot, + 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; +} + +std::unique_ptr<android::snapshot::ISnapshotWriter> +DynamicPartitionControlStub::OpenCowWriter( + const std::string& /*unsuffixed_partition_name*/, + const std::optional<std::string>& /*source_path*/, + bool /*is_append*/) { + return nullptr; +} + +bool DynamicPartitionControlStub::MapAllPartitions() { + return false; +} + +bool DynamicPartitionControlStub::UnmapAllPartitions() { + return false; +} + +bool DynamicPartitionControlStub::IsDynamicPartition( + const std::string& part_name, uint32_t slot) { + return false; +} + +bool DynamicPartitionControlStub::UpdateUsesSnapshotCompression() { + return false; +} + } // namespace chromeos_update_engine diff --git a/common/dynamic_partition_control_stub.h b/common/dynamic_partition_control_stub.h index d8e254ea..515ec7c9 100644 --- a/common/dynamic_partition_control_stub.h +++ b/common/dynamic_partition_control_stub.h @@ -21,6 +21,7 @@ #include <memory> #include <string> +#include <vector> #include "update_engine/common/dynamic_partition_control_interface.h" @@ -30,6 +31,7 @@ class DynamicPartitionControlStub : public DynamicPartitionControlInterface { public: FeatureFlag GetDynamicPartitionsFeatureFlag() override; FeatureFlag GetVirtualAbFeatureFlag() override; + FeatureFlag GetVirtualAbCompressionFeatureFlag() override; bool OptimizeOperation(const std::string& partition_name, const InstallOperation& operation, InstallOperation* optimized) override; @@ -46,8 +48,35 @@ class DynamicPartitionControlStub : public DynamicPartitionControlInterface { PrefsInterface* prefs, CleanupPreviousUpdateActionDelegateInterface* delegate) override; bool ResetUpdate(PrefsInterface* prefs) override; -}; + bool ListDynamicPartitionsForSlot( + uint32_t slot, + 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; + + std::unique_ptr<android::snapshot::ISnapshotWriter> OpenCowWriter( + const std::string& unsuffixed_partition_name, + const std::optional<std::string>&, + bool is_append) override; + + FileDescriptorPtr OpenCowFd(const std::string& unsuffixed_partition_name, + const std::optional<std::string>&, + bool is_append = false) override { + return nullptr; + } + + bool MapAllPartitions() override; + bool UnmapAllPartitions() override; + + bool IsDynamicPartition(const std::string& part_name, uint32_t slot) override; + bool UpdateUsesSnapshotCompression() 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 e473a05f..a8898886 100644 --- a/common/error_code.h +++ b/common/error_code.h @@ -85,6 +85,8 @@ enum class ErrorCode : int { kUnresolvedHostRecovered = 59, kNotEnoughSpace = 60, kDeviceCorrupted = 61, + kPackageExcludedFromUpdate = 62, + kPostInstallMountError = 63, // VERY IMPORTANT! When adding new error codes: // diff --git a/common/error_code_utils.cc b/common/error_code_utils.cc index 3fbf0feb..421544af 100644 --- a/common/error_code_utils.cc +++ b/common/error_code_utils.cc @@ -171,11 +171,15 @@ string ErrorCodeToString(ErrorCode code) { return "ErrorCode::kNotEnoughSpace"; case ErrorCode::kDeviceCorrupted: return "ErrorCode::kDeviceCorrupted"; + case ErrorCode::kPackageExcludedFromUpdate: + return "ErrorCode::kPackageExcludedFromUpdate"; + case ErrorCode::kPostInstallMountError: + return "ErrorCode::kPostInstallMountError"; // Don't add a default case to let the compiler warn about newly added // error codes which should be added here. } - return "Unknown error: " + base::UintToString(static_cast<unsigned>(code)); + return "Unknown error: " + base::NumberToString(static_cast<unsigned>(code)); } } // namespace utils diff --git a/common/excluder_interface.h b/common/excluder_interface.h new file mode 100644 index 00000000..1dfd227c --- /dev/null +++ b/common/excluder_interface.h @@ -0,0 +1,62 @@ +// +// 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_COMMON_EXCLUDER_INTERFACE_H_ +#define UPDATE_ENGINE_COMMON_EXCLUDER_INTERFACE_H_ + +#include <memory> +#include <string> + +#include <base/macros.h> + +namespace chromeos_update_engine { + +class PrefsInterface; + +// TODO(b/171829801): Move this interface to 'cros' directory. 'aosp' in no way +// is using this. Not even the stub implementation. +class ExcluderInterface { + public: + virtual ~ExcluderInterface() = default; + + // Returns true on successfuly excluding |name|, otherwise false. On a + // successful |Exclude()| the passed in |name| will be considered excluded + // and calls to |IsExcluded()| will return true. The exclusions are persisted. + virtual bool Exclude(const std::string& name) = 0; + + // Returns true if |name| reached the exclusion limit, otherwise false. + virtual bool IsExcluded(const std::string& name) = 0; + + // Returns true on sucessfully reseting the entire exclusion state, otherwise + // false. On a successful |Reset()| there will be no excluded |name| in the + // exclusion state. + virtual bool Reset() = 0; + + // Not copyable or movable + ExcluderInterface(const ExcluderInterface&) = delete; + ExcluderInterface& operator=(const ExcluderInterface&) = delete; + ExcluderInterface(ExcluderInterface&&) = delete; + ExcluderInterface& operator=(ExcluderInterface&&) = delete; + + protected: + ExcluderInterface() = default; +}; + +std::unique_ptr<ExcluderInterface> CreateExcluder(); + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_EXCLUDER_INTERFACE_H_ diff --git a/common/excluder_stub.cc b/common/excluder_stub.cc new file mode 100644 index 00000000..2b987fdf --- /dev/null +++ b/common/excluder_stub.cc @@ -0,0 +1,43 @@ +// +// 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/common/excluder_stub.h" + +#include <memory> + +#include "update_engine/common/prefs_interface.h" + +using std::string; + +namespace chromeos_update_engine { + +std::unique_ptr<ExcluderInterface> CreateExcluder() { + return std::make_unique<ExcluderStub>(); +} + +bool ExcluderStub::Exclude(const string& name) { + return true; +} + +bool ExcluderStub::IsExcluded(const string& name) { + return false; +} + +bool ExcluderStub::Reset() { + return true; +} + +} // namespace chromeos_update_engine diff --git a/common/excluder_stub.h b/common/excluder_stub.h new file mode 100644 index 00000000..2d5372a9 --- /dev/null +++ b/common/excluder_stub.h @@ -0,0 +1,46 @@ +// +// 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_COMMON_EXCLUDER_STUB_H_ +#define UPDATE_ENGINE_COMMON_EXCLUDER_STUB_H_ + +#include <string> + +#include "update_engine/common/excluder_interface.h" + +namespace chromeos_update_engine { + +// An implementation of the |ExcluderInterface| that does nothing. +class ExcluderStub : public ExcluderInterface { + public: + ExcluderStub() = default; + ~ExcluderStub() = default; + + // |ExcluderInterface| overrides. + bool Exclude(const std::string& name) override; + bool IsExcluded(const std::string& name) override; + bool Reset() override; + + // Not copyable or movable. + ExcluderStub(const ExcluderStub&) = delete; + ExcluderStub& operator=(const ExcluderStub&) = delete; + ExcluderStub(ExcluderStub&&) = delete; + ExcluderStub& operator=(ExcluderStub&&) = delete; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_EXCLUDER_STUB_H_ diff --git a/common/fake_boot_control.h b/common/fake_boot_control.h index bd9d9ca8..79e21390 100644 --- a/common/fake_boot_control.h +++ b/common/fake_boot_control.h @@ -48,16 +48,29 @@ class FakeBootControl : public BootControlInterface { bool GetPartitionDevice(const std::string& partition_name, BootControlInterface::Slot slot, - std::string* device) const override { - if (slot >= num_slots_) - return false; - auto part_it = devices_[slot].find(partition_name); - if (part_it == devices_[slot].end()) + bool not_in_payload, + std::string* device, + bool* is_dynamic) const override { + auto dev = + GetPartitionDevice(partition_name, slot, current_slot_, not_in_payload); + if (!dev.has_value()) { return false; - *device = part_it->second; + } + if (is_dynamic) { + *is_dynamic = dev->is_dynamic; + } + if (device) { + *device = dev->rw_device_path; + } 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]; } @@ -105,10 +118,29 @@ class FakeBootControl : public BootControlInterface { is_bootable_[slot] = bootable; } - DynamicPartitionControlInterface* GetDynamicPartitionControl() { + DynamicPartitionControlInterface* GetDynamicPartitionControl() override { return dynamic_partition_control_.get(); } + std::optional<PartitionDevice> GetPartitionDevice( + const std::string& partition_name, + uint32_t slot, + uint32_t current_slot, + bool not_in_payload = false) const override { + if (slot >= devices_.size()) { + return {}; + } + auto device_path = devices_[slot].find(partition_name); + if (device_path == devices_[slot].end()) { + return {}; + } + PartitionDevice device; + device.is_dynamic = false; + device.rw_device_path = device_path->second; + device.readonly_device_path = device.rw_device_path; + return device; + } + private: BootControlInterface::Slot num_slots_{2}; BootControlInterface::Slot current_slot_{0}; diff --git a/common/fake_clock.h b/common/fake_clock.h index 165ec4dd..9c47b579 100644 --- a/common/fake_clock.h +++ b/common/fake_clock.h @@ -26,11 +26,11 @@ class FakeClock : public ClockInterface { public: FakeClock() {} - base::Time GetWallclockTime() override { return wallclock_time_; } + base::Time GetWallclockTime() const override { return wallclock_time_; } - base::Time GetMonotonicTime() override { return monotonic_time_; } + base::Time GetMonotonicTime() const override { return monotonic_time_; } - base::Time GetBootTime() override { return boot_time_; } + base::Time GetBootTime() const override { return boot_time_; } void SetWallclockTime(const base::Time& time) { wallclock_time_ = time; } diff --git a/common/fake_hardware.h b/common/fake_hardware.h index 0b232daa..6c25183a 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 { @@ -73,9 +76,9 @@ class FakeHardware : public HardwareInterface { std::string GetHardwareClass() const override { return hardware_class_; } - std::string GetFirmwareVersion() const override { return firmware_version_; } - - std::string GetECVersion() const override { return ec_version_; } + std::string GetDeviceRequisition() const override { + return device_requisition_; + } int GetMinKernelKeyVersion() const override { return min_kernel_key_version_; @@ -104,15 +107,15 @@ class FakeHardware : public HardwareInterface { int GetPowerwashCount() const override { return powerwash_count_; } - bool SchedulePowerwash(bool is_rollback) override { + bool SchedulePowerwash(bool save_rollback_data) override { powerwash_scheduled_ = true; - is_rollback_powerwash_ = is_rollback; + save_rollback_data_ = save_rollback_data; return true; } bool CancelPowerwash() override { powerwash_scheduled_ = false; - is_rollback_powerwash_ = false; + save_rollback_data_ = false; return true; } @@ -169,12 +172,10 @@ class FakeHardware : public HardwareInterface { hardware_class_ = hardware_class; } - void SetFirmwareVersion(const std::string& firmware_version) { - firmware_version_ = firmware_version; + void SetDeviceRequisition(const std::string& requisition) { + device_requisition_ = requisition; } - void SetECVersion(const std::string& ec_version) { ec_version_ = ec_version; } - void SetMinKernelKeyVersion(int min_kernel_key_version) { min_kernel_key_version_ = min_kernel_key_version; } @@ -191,13 +192,39 @@ class FakeHardware : public HardwareInterface { build_timestamp_ = build_timestamp; } - void SetWarmReset(bool warm_reset) { warm_reset_ = warm_reset; } + void SetWarmReset(bool warm_reset) override { warm_reset_ = warm_reset; } + + void SetVbmetaDigestForInactiveSlot(bool reset) override {} // Getters to verify state. int GetMaxKernelKeyRollforward() const { return kernel_max_rollforward_; } bool GetIsRollbackPowerwashScheduled() const { - return powerwash_scheduled_ && is_rollback_powerwash_; + 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); + } + + const char* GetPartitionMountOptions( + const std::string& partition_name) const override { +#ifdef __ANDROID__ + // TODO(allight): This matches the declaration in hardware_android.cc but + // ideally shouldn't be duplicated. + return "defcontext=u:object_r:postinstall_file:s0"; +#else + return ""; +#endif } private: @@ -209,18 +236,18 @@ class FakeHardware : public HardwareInterface { // Jan 20, 2007 base::Time oobe_timestamp_{base::Time::FromTimeT(1169280000)}; std::string hardware_class_{"Fake HWID BLAH-1234"}; - std::string firmware_version_{"Fake Firmware v1.0.1"}; - std::string ec_version_{"Fake EC v1.0a"}; + std::string device_requisition_{"fake_requisition"}; int min_kernel_key_version_{kMinKernelKeyVersion}; int min_firmware_key_version_{kMinFirmwareKeyVersion}; int kernel_max_rollforward_{kKernelMaxRollforward}; int firmware_max_rollforward_{kFirmwareMaxRollforward}; int powerwash_count_{kPowerwashCountNotSet}; bool powerwash_scheduled_{false}; - bool is_rollback_powerwash_{false}; + 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/fake_prefs.cc b/common/fake_prefs.cc index c446e062..ea6ea60d 100644 --- a/common/fake_prefs.cc +++ b/common/fake_prefs.cc @@ -17,10 +17,12 @@ #include "update_engine/common/fake_prefs.h" #include <algorithm> +#include <utility> #include <gtest/gtest.h> using std::string; +using std::vector; using chromeos_update_engine::FakePrefs; @@ -65,8 +67,8 @@ bool FakePrefs::GetString(const string& key, string* value) const { return GetValue(key, value); } -bool FakePrefs::SetString(const string& key, const string& value) { - SetValue(key, value); +bool FakePrefs::SetString(const string& key, std::string_view value) { + SetValue(key, std::string(value)); return true; } @@ -105,6 +107,29 @@ bool FakePrefs::Delete(const string& key) { return true; } +bool FakePrefs::Delete(const string& key, const vector<string>& nss) { + bool success = Delete(key); + for (const auto& ns : nss) { + vector<string> ns_keys; + success = GetSubKeys(ns, &ns_keys) && success; + for (const auto& sub_key : ns_keys) { + auto last_key_seperator = sub_key.find_last_of(kKeySeparator); + if (last_key_seperator != string::npos && + key == sub_key.substr(last_key_seperator + 1)) { + success = Delete(sub_key) && success; + } + } + } + return success; +} + +bool FakePrefs::GetSubKeys(const string& ns, vector<string>* keys) const { + for (const auto& pr : values_) + if (pr.first.compare(0, ns.length(), ns) == 0) + keys->push_back(pr.first); + return true; +} + string FakePrefs::GetTypeName(PrefType type) { switch (type) { case PrefType::kString: @@ -125,10 +150,10 @@ void FakePrefs::CheckKeyType(const string& key, PrefType type) const { } template <typename T> -void FakePrefs::SetValue(const string& key, const T& value) { +void FakePrefs::SetValue(const string& key, T value) { CheckKeyType(key, PrefConsts<T>::type); values_[key].type = PrefConsts<T>::type; - values_[key].value.*(PrefConsts<T>::member) = value; + values_[key].value.*(PrefConsts<T>::member) = std::move(value); const auto observers_for_key = observers_.find(key); if (observers_for_key != observers_.end()) { std::vector<ObserverInterface*> copy_observers(observers_for_key->second); diff --git a/common/fake_prefs.h b/common/fake_prefs.h index b1c5b712..430c2914 100644 --- a/common/fake_prefs.h +++ b/common/fake_prefs.h @@ -19,6 +19,7 @@ #include <map> #include <string> +#include <string_view> #include <vector> #include <base/macros.h> @@ -40,7 +41,7 @@ class FakePrefs : public PrefsInterface { // PrefsInterface methods. bool GetString(const std::string& key, std::string* value) const override; - bool SetString(const std::string& key, const std::string& value) override; + bool SetString(const std::string& key, std::string_view value) override; bool GetInt64(const std::string& key, int64_t* value) const override; bool SetInt64(const std::string& key, const int64_t value) override; bool GetBoolean(const std::string& key, bool* value) const override; @@ -48,6 +49,11 @@ class FakePrefs : public PrefsInterface { bool Exists(const std::string& key) const override; bool Delete(const std::string& key) override; + bool Delete(const std::string& key, + const std::vector<std::string>& nss) override; + + bool GetSubKeys(const std::string& ns, + std::vector<std::string>* keys) const override; void AddObserver(const std::string& key, ObserverInterface* observer) override; @@ -91,7 +97,7 @@ class FakePrefs : public PrefsInterface { // Helper function to set a value of the passed |key|. It sets the type based // on the template parameter T. template <typename T> - void SetValue(const std::string& key, const T& value); + void SetValue(const std::string& key, T value); // Helper function to get a value from the map checking for invalid calls. // The function fails the test if you attempt to read a value defined as a diff --git a/common/file_fetcher.h b/common/file_fetcher.h index fbdfc327..bd390074 100644 --- a/common/file_fetcher.h +++ b/common/file_fetcher.h @@ -59,6 +59,12 @@ class FileFetcher : public HttpFetcher { void SetHeader(const std::string& header_name, const std::string& header_value) override {} + bool GetHeader(const std::string& header_name, + std::string* header_value) const override { + header_value->clear(); + return false; + } + // Suspend the asynchronous file read. void Pause() override; diff --git a/common/hardware_interface.h b/common/hardware_interface.h index d92a6fcc..4e820f1a 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, @@ -62,13 +64,9 @@ class HardwareInterface { // Returns the HWID or an empty string on error. virtual std::string GetHardwareClass() const = 0; - // Returns the firmware version or an empty string if the system is - // not running chrome os firmware. - virtual std::string GetFirmwareVersion() const = 0; - - // Returns the ec version or an empty string if the system is not - // running a custom chrome os ec. - virtual std::string GetECVersion() const = 0; + // Returns the OEM device requisition or an empty string if the system does + // not have a requisition, or if not running Chrome OS. + virtual std::string GetDeviceRequisition() const = 0; // Returns the minimum kernel key version that verified boot on Chrome OS // will allow to boot. This is the value of crossystem tpm_kernver. Returns @@ -102,9 +100,9 @@ class HardwareInterface { virtual int GetPowerwashCount() const = 0; // Signals that a powerwash (stateful partition wipe) should be performed - // after reboot. If |is_rollback| is true additional state is preserved - // during shutdown that can be restored after the powerwash. - virtual bool SchedulePowerwash(bool is_rollback) = 0; + // after reboot. If |save_rollback_data| is true additional state is + // preserved during shutdown that can be restored after the powerwash. + virtual bool SchedulePowerwash(bool save_rollback_data) = 0; // Cancel the powerwash operation scheduled to be performed on next boot. virtual bool CancelPowerwash() = 0; @@ -138,6 +136,33 @@ class HardwareInterface { // 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; + + // If not reset, sets the vbmeta digest of the inactive slot as a sysprop. + // Otherwise, clears the sysprop. + virtual void SetVbmetaDigestForInactiveSlot(bool 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; + + virtual const char* GetPartitionMountOptions( + const std::string& partition_name) const = 0; }; } // namespace chromeos_update_engine diff --git a/common/hash_calculator.cc b/common/hash_calculator.cc index d010a532..60812d56 100644 --- a/common/hash_calculator.cc +++ b/common/hash_calculator.cc @@ -95,6 +95,11 @@ bool HashCalculator::RawHashOfData(const brillo::Blob& data, return RawHashOfBytes(data.data(), data.size(), out_hash); } +bool HashCalculator::RawHashOfFile(const string& name, brillo::Blob* out_hash) { + const auto file_size = utils::FileSize(name); + return RawHashOfFile(name, file_size, out_hash) == file_size; +} + off_t HashCalculator::RawHashOfFile(const string& name, off_t length, brillo::Blob* out_hash) { diff --git a/common/hash_calculator.h b/common/hash_calculator.h index b7e4d86c..44261284 100644 --- a/common/hash_calculator.h +++ b/common/hash_calculator.h @@ -75,6 +75,7 @@ class HashCalculator { static off_t RawHashOfFile(const std::string& name, off_t length, brillo::Blob* out_hash); + static bool RawHashOfFile(const std::string& name, brillo::Blob* out_hash); private: // If non-empty, the final raw hash. Will only be set to non-empty when diff --git a/common/hash_calculator_unittest.cc b/common/hash_calculator_unittest.cc index e8f73d5c..fe7d543d 100644 --- a/common/hash_calculator_unittest.cc +++ b/common/hash_calculator_unittest.cc @@ -104,7 +104,7 @@ TEST_F(HashCalculatorTest, BigTest) { } TEST_F(HashCalculatorTest, UpdateFileSimpleTest) { - test_utils::ScopedTempFile data_file("data.XXXXXX"); + ScopedTempFile data_file("data.XXXXXX"); ASSERT_TRUE(test_utils::WriteFileString(data_file.path(), "hi")); for (const int length : {-1, 2, 10}) { @@ -126,7 +126,7 @@ TEST_F(HashCalculatorTest, UpdateFileSimpleTest) { } TEST_F(HashCalculatorTest, RawHashOfFileSimpleTest) { - test_utils::ScopedTempFile data_file("data.XXXXXX"); + ScopedTempFile data_file("data.XXXXXX"); ASSERT_TRUE(test_utils::WriteFileString(data_file.path(), "hi")); for (const int length : {-1, 2, 10}) { diff --git a/common/http_common.cc b/common/http_common.cc index 5f234b0b..c8bac477 100644 --- a/common/http_common.cc +++ b/common/http_common.cc @@ -21,6 +21,7 @@ #include <cstdlib> #include <base/macros.h> +#include <base/stl_util.h> namespace chromeos_update_engine { @@ -56,7 +57,7 @@ const char* GetHttpResponseDescription(HttpResponseCode code) { bool is_found = false; size_t i; - for (i = 0; i < arraysize(http_response_table); i++) + for (i = 0; i < base::size(http_response_table); i++) if ((is_found = (http_response_table[i].code == code))) break; @@ -77,7 +78,7 @@ const char* GetHttpContentTypeString(HttpContentType type) { bool is_found = false; size_t i; - for (i = 0; i < arraysize(http_content_type_table); i++) + for (i = 0; i < base::size(http_content_type_table); i++) if ((is_found = (http_content_type_table[i].type == type))) break; diff --git a/common/http_fetcher.h b/common/http_fetcher.h index 2b4fc833..7fa5f098 100644 --- a/common/http_fetcher.h +++ b/common/http_fetcher.h @@ -28,6 +28,7 @@ #include <brillo/message_loops/message_loop.h> #include "update_engine/common/http_common.h" +#include "update_engine/common/metrics_constants.h" #include "update_engine/common/proxy_resolver.h" // This class is a simple wrapper around an HTTP library (libcurl). We can @@ -58,6 +59,12 @@ class HttpFetcher { HttpFetcherDelegate* delegate() const { return delegate_; } int http_response_code() const { return http_response_code_; } + // Returns additional error code that can't be expressed in terms of an HTTP + // response code. For example, if there was a specific internal error code in + // the objects used in the implementation of this class (like libcurl) that we + // are interested about, we can communicate it through this value. + ErrorCode GetAuxiliaryErrorCode() const { return auxiliary_error_code_; } + // Optional: Post data to the server. The HttpFetcher should make a copy // of this data and upload it via HTTP POST during the transfer. The type of // the data is necessary for properly setting the Content-Type HTTP header. @@ -99,6 +106,14 @@ class HttpFetcher { virtual void SetHeader(const std::string& header_name, const std::string& header_value) = 0; + // Only used for testing. + // If |header_name| is set, the value will be set into |header_value|. + // On success the boolean true will be returned, hoewever on failture to find + // the |header_name| in the header the return value will be false. The state + // in which |header_value| is left in for failures is an empty string. + virtual bool GetHeader(const std::string& header_name, + std::string* header_value) const = 0; + // If data is coming in too quickly, you can call Pause() to pause the // transfer. The delegate will not have ReceivedBytes() called while // an HttpFetcher is paused. @@ -150,6 +165,10 @@ class HttpFetcher { // set to the response code when the transfer is complete. int http_response_code_; + // Set when there is an error that can't be expressed in the form of + // |http_response_code_|. + ErrorCode auxiliary_error_code_{ErrorCode::kSuccess}; + // The delegate; may be null. HttpFetcherDelegate* delegate_; diff --git a/common/http_fetcher_unittest.cc b/common/http_fetcher_unittest.cc index 237ea209..99ea99bd 100644 --- a/common/http_fetcher_unittest.cc +++ b/common/http_fetcher_unittest.cc @@ -28,15 +28,25 @@ #include <base/bind.h> #include <base/location.h> #include <base/logging.h> +#if BASE_VER < 780000 // Android #include <base/message_loop/message_loop.h> +#endif // BASE_VER < 780000 +#include <base/stl_util.h> #include <base/strings/string_number_conversions.h> #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> +#if BASE_VER >= 780000 // CrOS +#include <base/task/single_thread_task_executor.h> +#endif // BASE_VER >= 780000 #include <base/time/time.h> #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> @@ -359,7 +369,7 @@ class FileFetcherTest : public AnyHttpFetcherTest { HttpServer* CreateServer() override { return new NullHttpServer; } private: - test_utils::ScopedTempFile temp_file_{"ue_file_fetcher.XXXXXX"}; + ScopedTempFile temp_file_{"ue_file_fetcher.XXXXXX"}; }; class MultiRangeHttpFetcherOverFileFetcherTest : public FileFetcherTest { @@ -398,8 +408,13 @@ class MultiRangeHttpFetcherOverFileFetcherTest : public FileFetcherTest { template <typename T> class HttpFetcherTest : public ::testing::Test { public: +#if BASE_VER < 780000 // Android base::MessageLoopForIO base_loop_; brillo::BaseMessageLoop loop_{&base_loop_}; +#else // Chrome OS + base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO}; + brillo::BaseMessageLoop loop_{base_loop_.task_runner()}; +#endif // BASE_VER < 780000 T test_; @@ -1049,7 +1064,7 @@ TYPED_TEST(HttpFetcherTest, SimpleRedirectTest) { unique_ptr<HttpServer> server(this->test_.CreateServer()); ASSERT_TRUE(server->started_); - for (size_t c = 0; c < arraysize(kRedirectCodes); ++c) { + for (size_t c = 0; c < base::size(kRedirectCodes); ++c) { const string url = base::StringPrintf( "/redirect/%d/download/%d", kRedirectCodes[c], kMediumLength); RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher()); @@ -1066,7 +1081,7 @@ TYPED_TEST(HttpFetcherTest, MaxRedirectTest) { string url; for (int r = 0; r < kDownloadMaxRedirects; r++) { url += base::StringPrintf("/redirect/%d", - kRedirectCodes[r % arraysize(kRedirectCodes)]); + kRedirectCodes[r % base::size(kRedirectCodes)]); } url += base::StringPrintf("/download/%d", kMediumLength); RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher()); @@ -1082,7 +1097,7 @@ TYPED_TEST(HttpFetcherTest, BeyondMaxRedirectTest) { string url; for (int r = 0; r < kDownloadMaxRedirects + 1; r++) { url += base::StringPrintf("/redirect/%d", - kRedirectCodes[r % arraysize(kRedirectCodes)]); + kRedirectCodes[r % base::size(kRedirectCodes)]); } url += base::StringPrintf("/download/%d", kMediumLength); RedirectTest(server.get(), false, url, this->test_.NewLargeFetcher()); diff --git a/common/hwid_override.cc b/common/hwid_override.cc index 8800e948..1bb0f8f5 100644 --- a/common/hwid_override.cc +++ b/common/hwid_override.cc @@ -16,14 +16,12 @@ #include "update_engine/common/hwid_override.h" -#include <map> #include <string> #include <base/files/file_path.h> #include <base/files/file_util.h> #include <brillo/key_value_store.h> -using std::map; using std::string; namespace chromeos_update_engine { diff --git a/logging.h b/common/logging.h index c9e7483c..c9e7483c 100644 --- a/logging.h +++ b/common/logging.h diff --git a/metrics_constants.h b/common/metrics_constants.h index 137143ab..b7633b90 100644 --- a/metrics_constants.h +++ b/common/metrics_constants.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_METRICS_CONSTANTS_H_ -#define UPDATE_ENGINE_METRICS_CONSTANTS_H_ +#ifndef UPDATE_ENGINE_COMMON_METRICS_CONSTANTS_H_ +#define UPDATE_ENGINE_COMMON_METRICS_CONSTANTS_H_ namespace chromeos_update_engine { @@ -106,7 +106,7 @@ enum class AttemptResult { kUpdateCanceled, // Update canceled by the user. kUpdateSucceededNotActive, // Update succeeded but the new slot is not // active. - + kUpdateSkipped, // Current update skipped. kNumConstants, kUnset = -1 @@ -119,12 +119,12 @@ enum class ConnectionType { kUnknown = 0, // Unknown. kEthernet = 1, // Ethernet. kWifi = 2, // Wireless. - kWimax = 3, // WiMax. - kBluetooth = 4, // Bluetooth. kCellular = 5, // Cellular. kTetheredEthernet = 6, // Tethered (Ethernet). kTetheredWifi = 7, // Tethered (Wifi). kDisconnected = 8, // Disconnected. + // deprecated: kWimax = 3, + // deprecated: kBluetooth = 4, kNumConstants, kUnset = -1 @@ -144,4 +144,4 @@ enum class RollbackResult { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_METRICS_CONSTANTS_H_ +#endif // UPDATE_ENGINE_COMMON_METRICS_CONSTANTS_H_ diff --git a/metrics_reporter_interface.h b/common/metrics_reporter_interface.h index fce8bfd7..a7a91a54 100644 --- a/metrics_reporter_interface.h +++ b/common/metrics_reporter_interface.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_METRICS_REPORTER_INTERFACE_H_ -#define UPDATE_ENGINE_METRICS_REPORTER_INTERFACE_H_ +#ifndef UPDATE_ENGINE_COMMON_METRICS_REPORTER_INTERFACE_H_ +#define UPDATE_ENGINE_COMMON_METRICS_REPORTER_INTERFACE_H_ #include <memory> #include <string> @@ -23,27 +23,20 @@ #include <base/time/time.h> #include "update_engine/common/constants.h" +#include "update_engine/common/dynamic_partition_control_interface.h" #include "update_engine/common/error_code.h" -#include "update_engine/metrics_constants.h" -#include "update_engine/system_state.h" +#include "update_engine/common/metrics_constants.h" +#include "update_engine/payload_consumer/install_plan.h" namespace chromeos_update_engine { enum class ServerToCheck; enum class CertificateCheckResult; -namespace metrics { - -std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter(); - -} // namespace metrics - class MetricsReporterInterface { public: virtual ~MetricsReporterInterface() = default; - virtual void Initialize() = 0; - // Helper function to report metrics related to user-initiated rollback. The // following metrics are reported: // @@ -94,7 +87,6 @@ class MetricsReporterInterface { // if it's set, |kMetricCheckRollbackTargetVersion| reports the same, but only // if rollback is also allowed using enterprise policy. virtual void ReportUpdateCheckMetrics( - SystemState* system_state, metrics::CheckResult result, metrics::CheckReaction reaction, metrics::DownloadErrorCode download_error_code) = 0; @@ -122,8 +114,7 @@ class MetricsReporterInterface { // |kMetricAttemptTimeSinceLastAttemptUptimeMinutes| metrics are // automatically calculated and reported by maintaining persistent and // process-local state variables. - virtual void ReportUpdateAttemptMetrics(SystemState* system_state, - int attempt_number, + virtual void ReportUpdateAttemptMetrics(int attempt_number, PayloadType payload_type, base::TimeDelta duration, base::TimeDelta duration_uptime, @@ -244,6 +235,14 @@ class MetricsReporterInterface { bool has_time_restriction_policy, int time_to_update_days) = 0; }; +namespace metrics { + +std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter( + DynamicPartitionControlInterface* dynamic_partition_control, + const InstallPlan* install_plan); + +} // namespace metrics + } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_METRICS_REPORTER_INTERFACE_H_ +#endif // UPDATE_ENGINE_COMMON_METRICS_REPORTER_INTERFACE_H_ diff --git a/metrics_reporter_stub.cc b/common/metrics_reporter_stub.cc index 81664a53..61559d95 100644 --- a/metrics_reporter_stub.cc +++ b/common/metrics_reporter_stub.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/metrics_reporter_stub.h" +#include "update_engine/common/metrics_reporter_stub.h" #include <memory> @@ -22,7 +22,9 @@ namespace chromeos_update_engine { namespace metrics { -std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter() { +std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter( + DynamicPartitionControlInterface* dynamic_partition_control, + const InstallPlan* install_plan) { return std::make_unique<MetricsReporterStub>(); } diff --git a/metrics_reporter_stub.h b/common/metrics_reporter_stub.h index 25660b5c..80cf4693 100644 --- a/metrics_reporter_stub.h +++ b/common/metrics_reporter_stub.h @@ -14,14 +14,14 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_METRICS_REPORTER_STUB_H_ -#define UPDATE_ENGINE_METRICS_REPORTER_STUB_H_ +#ifndef UPDATE_ENGINE_COMMON_METRICS_REPORTER_STUB_H_ +#define UPDATE_ENGINE_COMMON_METRICS_REPORTER_STUB_H_ #include <string> #include "update_engine/common/error_code.h" -#include "update_engine/metrics_constants.h" -#include "update_engine/metrics_reporter_interface.h" +#include "update_engine/common/metrics_constants.h" +#include "update_engine/common/metrics_reporter_interface.h" namespace chromeos_update_engine { @@ -31,8 +31,6 @@ class MetricsReporterStub : public MetricsReporterInterface { ~MetricsReporterStub() override = default; - void Initialize() override {} - void ReportRollbackMetrics(metrics::RollbackResult result) override {} void ReportEnterpriseRollbackMetrics( @@ -41,13 +39,11 @@ class MetricsReporterStub : public MetricsReporterInterface { void ReportDailyMetrics(base::TimeDelta os_age) override {} void ReportUpdateCheckMetrics( - SystemState* system_state, metrics::CheckResult result, metrics::CheckReaction reaction, metrics::DownloadErrorCode download_error_code) override {} - void ReportUpdateAttemptMetrics(SystemState* system_state, - int attempt_number, + void ReportUpdateAttemptMetrics(int attempt_number, PayloadType payload_type, base::TimeDelta duration, base::TimeDelta duration_uptime, @@ -100,4 +96,4 @@ class MetricsReporterStub : public MetricsReporterInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_METRICS_REPORTER_STUB_H_ +#endif // UPDATE_ENGINE_COMMON_METRICS_REPORTER_STUB_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_boot_control.h b/common/mock_boot_control.h new file mode 100644 index 00000000..f75ce5ec --- /dev/null +++ b/common/mock_boot_control.h @@ -0,0 +1,74 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_COMMON_MOCK_BOOT_CONTROL_H_ +#define UPDATE_ENGINE_COMMON_MOCK_BOOT_CONTROL_H_ + +#include <memory> +#include <string> + +#include <gmock/gmock.h> + +#include "update_engine/common/boot_control_stub.h" + +namespace chromeos_update_engine { + +class MockBootControl final : public BootControlStub { + public: + MOCK_METHOD(bool, + IsSlotMarkedSuccessful, + (BootControlInterface::Slot), + (const override)); + MOCK_METHOD(unsigned int, GetNumSlots, (), (const override)); + MOCK_METHOD(BootControlInterface::Slot, GetCurrentSlot, (), (const override)); + MOCK_METHOD(bool, + GetPartitionDevice, + (const std::string&, Slot, bool, std::string*, bool*), + (const override)); + MOCK_METHOD(bool, + GetPartitionDevice, + (const std::string&, BootControlInterface::Slot, std::string*), + (const override)); + MOCK_METHOD(std::optional<PartitionDevice>, + GetPartitionDevice, + (const std::string&, uint32_t, uint32_t, bool), + (const override)); + + MOCK_METHOD(bool, + IsSlotBootable, + (BootControlInterface::Slot), + (const override)); + MOCK_METHOD(bool, + MarkSlotUnbootable, + (BootControlInterface::Slot), + (override)); + MOCK_METHOD(bool, + SetActiveBootSlot, + (BootControlInterface::Slot), + (override)); + MOCK_METHOD(bool, + MarkBootSuccessfulAsync, + (base::Callback<void(bool)>), + (override)); + MOCK_METHOD(DynamicPartitionControlInterface*, + GetDynamicPartitionControl, + (), + (override)); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_MOCK_BOOT_CONTROL_H_ diff --git a/payload_consumer/mock_download_action.h b/common/mock_download_action.h index 3abb809e..ecda9a31 100644 --- a/payload_consumer/mock_download_action.h +++ b/common/mock_download_action.h @@ -14,15 +14,15 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_MOCK_DOWNLOAD_ACTION_H_ -#define UPDATE_ENGINE_PAYLOAD_CONSUMER_MOCK_DOWNLOAD_ACTION_H_ +#ifndef UPDATE_ENGINE_COMMON_MOCK_DOWNLOAD_ACTION_H_ +#define UPDATE_ENGINE_COMMON_MOCK_DOWNLOAD_ACTION_H_ #include <stdint.h> #include <gmock/gmock.h> +#include "update_engine/common/download_action.h" #include "update_engine/common/error_code.h" -#include "update_engine/payload_consumer/download_action.h" namespace chromeos_update_engine { @@ -38,4 +38,4 @@ class MockDownloadActionDelegate : public DownloadActionDelegate { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_MOCK_DOWNLOAD_ACTION_H_ +#endif // UPDATE_ENGINE_COMMON_MOCK_DOWNLOAD_ACTION_H_ diff --git a/common/mock_dynamic_partition_control.h b/common/mock_dynamic_partition_control.h new file mode 100644 index 00000000..bfd1b0c6 --- /dev/null +++ b/common/mock_dynamic_partition_control.h @@ -0,0 +1,89 @@ +// +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <stdint.h> + +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include <gmock/gmock.h> + +#include "update_engine/common/dynamic_partition_control_interface.h" +#include "update_engine/payload_consumer/file_descriptor.h" + +namespace chromeos_update_engine { + +class MockDynamicPartitionControl : public DynamicPartitionControlInterface { + public: + MOCK_METHOD(void, Cleanup, (), (override)); + MOCK_METHOD(bool, GetDeviceDir, (std::string*), (override)); + MOCK_METHOD(FeatureFlag, GetDynamicPartitionsFeatureFlag, (), (override)); + MOCK_METHOD(FeatureFlag, GetVirtualAbCompressionFeatureFlag, (), (override)); + MOCK_METHOD(FeatureFlag, GetVirtualAbFeatureFlag, (), (override)); + MOCK_METHOD(bool, FinishUpdate, (bool), (override)); + MOCK_METHOD(FileDescriptorPtr, + OpenCowFd, + (const std::string& unsuffixed_partition_name, + const std::optional<std::string>& source_path, + bool is_append), + (override)); + MOCK_METHOD(bool, MapAllPartitions, (), (override)); + MOCK_METHOD(bool, UnmapAllPartitions, (), (override)); + + MOCK_METHOD(bool, + OptimizeOperation, + (const std::string&, const InstallOperation&, InstallOperation*), + (override)); + + std::unique_ptr<android::snapshot::ISnapshotWriter> OpenCowWriter( + const std::string& unsuffixed_partition_name, + const std::optional<std::string>&, + bool is_append = false) override { + return nullptr; + } + + MOCK_METHOD( + bool, + PreparePartitionsForUpdate, + (uint32_t, uint32_t, const DeltaArchiveManifest&, bool, uint64_t*), + (override)); + + MOCK_METHOD(bool, ResetUpdate, (PrefsInterface*), (override)); + MOCK_METHOD(std::unique_ptr<AbstractAction>, + GetCleanupPreviousUpdateAction, + (BootControlInterface*, + PrefsInterface*, + CleanupPreviousUpdateActionDelegateInterface*), + (override)); + MOCK_METHOD(bool, + ListDynamicPartitionsForSlot, + (uint32_t, uint32_t, std::vector<std::string>*), + (override)); + + MOCK_METHOD(bool, + VerifyExtentsForUntouchedPartitions, + (uint32_t, uint32_t, const std::vector<std::string>&), + (override)); + MOCK_METHOD(bool, + IsDynamicPartition, + (const std::string&, uint32_t slot), + (override)); + MOCK_METHOD(bool, UpdateUsesSnapshotCompression, (), (override)); +}; + +} // namespace chromeos_update_engine diff --git a/common/mock_excluder.h b/common/mock_excluder.h new file mode 100644 index 00000000..560ba0d8 --- /dev/null +++ b/common/mock_excluder.h @@ -0,0 +1,37 @@ +// +// 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_COMMON_MOCK_APP_EXCLUDER_H_ +#define UPDATE_ENGINE_COMMON_MOCK_APP_EXCLUDER_H_ + +#include "update_engine/common/excluder_interface.h" + +#include <string> + +#include <gmock/gmock.h> + +namespace chromeos_update_engine { + +class MockExcluder : public ExcluderInterface { + public: + MOCK_METHOD(bool, Exclude, (const std::string&), (override)); + MOCK_METHOD(bool, IsExcluded, (const std::string&), (override)); + MOCK_METHOD(bool, Reset, (), (override)); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_MOCK_APP_EXCLUDER_H_ diff --git a/common/mock_hardware.h b/common/mock_hardware.h index 84c0c5ba..071906b5 100644 --- a/common/mock_hardware.h +++ b/common/mock_hardware.h @@ -45,11 +45,6 @@ class MockHardware : public HardwareInterface { ON_CALL(*this, GetHardwareClass()) .WillByDefault( testing::Invoke(&fake_, &FakeHardware::GetHardwareClass)); - ON_CALL(*this, GetFirmwareVersion()) - .WillByDefault( - testing::Invoke(&fake_, &FakeHardware::GetFirmwareVersion)); - ON_CALL(*this, GetECVersion()) - .WillByDefault(testing::Invoke(&fake_, &FakeHardware::GetECVersion)); ON_CALL(*this, GetMinKernelKeyVersion()) .WillByDefault( testing::Invoke(&fake_, &FakeHardware::GetMinKernelKeyVersion)); @@ -90,8 +85,6 @@ class MockHardware : public HardwareInterface { MOCK_CONST_METHOD0(IsOOBEEnabled, bool()); MOCK_CONST_METHOD1(IsOOBEComplete, bool(base::Time* out_time_of_oobe)); MOCK_CONST_METHOD0(GetHardwareClass, std::string()); - MOCK_CONST_METHOD0(GetFirmwareVersion, std::string()); - MOCK_CONST_METHOD0(GetECVersion, std::string()); MOCK_CONST_METHOD0(GetMinKernelKeyVersion, int()); MOCK_CONST_METHOD0(GetMinFirmwareKeyVersion, int()); MOCK_CONST_METHOD0(GetMaxFirmwareKeyRollforward, int()); 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 492e6cee..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; @@ -89,6 +89,12 @@ class MockHttpFetcher : public HttpFetcher { void SetHeader(const std::string& header_name, const std::string& header_value) override; + bool GetHeader(const std::string& header_name, + std::string* header_value) const override { + header_value->clear(); + return false; + } + // Return the value of the header |header_name| or the empty string if not // set. std::string GetHeader(const std::string& header_name) const; @@ -107,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, @@ -123,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_; @@ -134,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/mock_metrics_reporter.h b/common/mock_metrics_reporter.h index baf3a784..1bb1e84b 100644 --- a/mock_metrics_reporter.h +++ b/common/mock_metrics_reporter.h @@ -14,14 +14,14 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_MOCK_METRICS_REPORTER_H_ -#define UPDATE_ENGINE_MOCK_METRICS_REPORTER_H_ +#ifndef UPDATE_ENGINE_COMMON_MOCK_METRICS_REPORTER_H_ +#define UPDATE_ENGINE_COMMON_MOCK_METRICS_REPORTER_H_ #include <string> #include <gmock/gmock.h> -#include "update_engine/metrics_reporter_interface.h" +#include "update_engine/common/metrics_reporter_interface.h" namespace chromeos_update_engine { @@ -36,15 +36,13 @@ class MockMetricsReporter : public MetricsReporterInterface { MOCK_METHOD1(ReportDailyMetrics, void(base::TimeDelta os_age)); - MOCK_METHOD4(ReportUpdateCheckMetrics, - void(SystemState* system_state, - metrics::CheckResult result, + MOCK_METHOD3(ReportUpdateCheckMetrics, + void(metrics::CheckResult result, metrics::CheckReaction reaction, metrics::DownloadErrorCode download_error_code)); - MOCK_METHOD8(ReportUpdateAttemptMetrics, - void(SystemState* system_state, - int attempt_number, + MOCK_METHOD7(ReportUpdateAttemptMetrics, + void(int attempt_number, PayloadType payload_type, base::TimeDelta duration, base::TimeDelta duration_uptime, @@ -96,4 +94,4 @@ class MockMetricsReporter : public MetricsReporterInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_MOCK_METRICS_REPORTER_H_ +#endif // UPDATE_ENGINE_COMMON_MOCK_METRICS_REPORTER_H_ diff --git a/common/mock_prefs.h b/common/mock_prefs.h index 2582e194..49431fbd 100644 --- a/common/mock_prefs.h +++ b/common/mock_prefs.h @@ -18,6 +18,7 @@ #define UPDATE_ENGINE_COMMON_MOCK_PREFS_H_ #include <string> +#include <vector> #include <gmock/gmock.h> @@ -30,8 +31,7 @@ class MockPrefs : public PrefsInterface { public: MOCK_CONST_METHOD2(GetString, bool(const std::string& key, std::string* value)); - MOCK_METHOD2(SetString, - bool(const std::string& key, const std::string& value)); + MOCK_METHOD2(SetString, bool(const std::string& key, std::string_view value)); MOCK_CONST_METHOD2(GetInt64, bool(const std::string& key, int64_t* value)); MOCK_METHOD2(SetInt64, bool(const std::string& key, const int64_t value)); @@ -40,6 +40,12 @@ class MockPrefs : public PrefsInterface { MOCK_CONST_METHOD1(Exists, bool(const std::string& key)); MOCK_METHOD1(Delete, bool(const std::string& key)); + MOCK_METHOD2(Delete, + bool(const std::string& key, + const std::vector<std::string>& nss)); + + MOCK_CONST_METHOD2(GetSubKeys, + bool(const std::string&, std::vector<std::string>*)); MOCK_METHOD2(AddObserver, void(const std::string& key, ObserverInterface*)); MOCK_METHOD2(RemoveObserver, diff --git a/mock_service_observer.h b/common/mock_service_observer.h index e434eabe..2c895f98 100644 --- a/mock_service_observer.h +++ b/common/mock_service_observer.h @@ -14,11 +14,11 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_MOCK_SERVICE_OBSERVER_H_ -#define UPDATE_ENGINE_MOCK_SERVICE_OBSERVER_H_ +#ifndef UPDATE_ENGINE_COMMON_MOCK_SERVICE_OBSERVER_H_ +#define UPDATE_ENGINE_COMMON_MOCK_SERVICE_OBSERVER_H_ #include <gmock/gmock.h> -#include "update_engine/service_observer_interface.h" +#include "update_engine/common/service_observer_interface.h" namespace chromeos_update_engine { @@ -32,4 +32,4 @@ class MockServiceObserver : public ServiceObserverInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_MOCK_SERVICE_OBSERVER_H_ +#endif // UPDATE_ENGINE_COMMON_MOCK_SERVICE_OBSERVER_H_ diff --git a/common/multi_range_http_fetcher.h b/common/multi_range_http_fetcher.h index f57ea7f6..ef32f0d5 100644 --- a/common/multi_range_http_fetcher.h +++ b/common/multi_range_http_fetcher.h @@ -83,6 +83,11 @@ class MultiRangeHttpFetcher : public HttpFetcher, public HttpFetcherDelegate { base_fetcher_->SetHeader(header_name, header_value); } + bool GetHeader(const std::string& header_name, + std::string* header_value) const override { + return base_fetcher_->GetHeader(header_name, header_value); + } + void Pause() override { base_fetcher_->Pause(); } void Unpause() override { base_fetcher_->Unpause(); } diff --git a/network_selector.h b/common/network_selector.h index 22aed8e1..bfc09c54 100644 --- a/network_selector.h +++ b/common/network_selector.h @@ -14,12 +14,12 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_NETWORK_SELECTOR_H_ -#define UPDATE_ENGINE_NETWORK_SELECTOR_H_ +#ifndef UPDATE_ENGINE_COMMON_NETWORK_SELECTOR_H_ +#define UPDATE_ENGINE_COMMON_NETWORK_SELECTOR_H_ #include <memory> -#include "update_engine/network_selector_interface.h" +#include "update_engine/common/network_selector_interface.h" namespace chromeos_update_engine { namespace network { @@ -30,4 +30,4 @@ std::unique_ptr<NetworkSelectorInterface> CreateNetworkSelector(); } // namespace network } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_NETWORK_SELECTOR_H_ +#endif // UPDATE_ENGINE_COMMON_NETWORK_SELECTOR_H_ diff --git a/network_selector_interface.h b/common/network_selector_interface.h index bd0948f9..42ce32e2 100644 --- a/network_selector_interface.h +++ b/common/network_selector_interface.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_NETWORK_SELECTOR_INTERFACE_H_ -#define UPDATE_ENGINE_NETWORK_SELECTOR_INTERFACE_H_ +#ifndef UPDATE_ENGINE_COMMON_NETWORK_SELECTOR_INTERFACE_H_ +#define UPDATE_ENGINE_COMMON_NETWORK_SELECTOR_INTERFACE_H_ #include <cstdint> @@ -45,4 +45,4 @@ class NetworkSelectorInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_NETWORK_SELECTOR_INTERFACE_H_ +#endif // UPDATE_ENGINE_COMMON_NETWORK_SELECTOR_INTERFACE_H_ diff --git a/network_selector_stub.cc b/common/network_selector_stub.cc index 67925f41..24c0e251 100644 --- a/network_selector_stub.cc +++ b/common/network_selector_stub.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/network_selector_stub.h" +#include "update_engine/common/network_selector_stub.h" #include <memory> @@ -24,14 +24,14 @@ namespace chromeos_update_engine { namespace network { -// Factory defined in network_selector.h. +// Factory defined in common/network_selector.h. std::unique_ptr<NetworkSelectorInterface> CreateNetworkSelector() { return std::make_unique<NetworkSelectorStub>(); } } // namespace network -// Defined in network_selector_interface.h. +// Defined in common/network_selector_interface.h. const NetworkId kDefaultNetworkId = 0; bool NetworkSelectorStub::SetProcessNetwork(NetworkId network_id) { diff --git a/network_selector_stub.h b/common/network_selector_stub.h index b3f7b487..b32df919 100644 --- a/network_selector_stub.h +++ b/common/network_selector_stub.h @@ -14,12 +14,12 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_NETWORK_SELECTOR_STUB_H_ -#define UPDATE_ENGINE_NETWORK_SELECTOR_STUB_H_ +#ifndef UPDATE_ENGINE_COMMON_NETWORK_SELECTOR_STUB_H_ +#define UPDATE_ENGINE_COMMON_NETWORK_SELECTOR_STUB_H_ #include <base/macros.h> -#include "update_engine/network_selector_interface.h" +#include "update_engine/common/network_selector_interface.h" namespace chromeos_update_engine { @@ -37,4 +37,4 @@ class NetworkSelectorStub final : public NetworkSelectorInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_NETWORK_SELECTOR_STUB_H_ +#endif // UPDATE_ENGINE_COMMON_NETWORK_SELECTOR_STUB_H_ diff --git a/common/platform_constants.h b/common/platform_constants.h index 243af69e..06399e5c 100644 --- a/common/platform_constants.h +++ b/common/platform_constants.h @@ -54,9 +54,11 @@ extern const char kOmahaResponseDeadlineFile[]; // The stateful directory used by update_engine. extern const char kNonVolatileDirectory[]; -// Options passed to the filesystem when mounting the new partition during -// postinstall. -extern const char kPostinstallMountOptions[]; +#ifdef __ANDROID_RECOVERY__ +constexpr bool kIsRecovery = true; +#else +constexpr bool kIsRecovery = false; +#endif } // namespace constants } // namespace chromeos_update_engine diff --git a/common/prefs.cc b/common/prefs.cc index 12d06c0c..1e06be44 100644 --- a/common/prefs.cc +++ b/common/prefs.cc @@ -18,22 +18,44 @@ #include <algorithm> +#include <base/files/file_enumerator.h> #include <base/files/file_util.h> #include <base/logging.h> #include <base/strings/string_number_conversions.h> +#include <base/strings/string_split.h> #include <base/strings/string_util.h> #include "update_engine/common/utils.h" using std::string; +using std::vector; namespace chromeos_update_engine { +namespace { + +void DeleteEmptyDirectories(const base::FilePath& path) { + base::FileEnumerator path_enum( + path, false /* recursive */, base::FileEnumerator::DIRECTORIES); + for (base::FilePath dir_path = path_enum.Next(); !dir_path.empty(); + dir_path = path_enum.Next()) { + DeleteEmptyDirectories(dir_path); + if (base::IsDirectoryEmpty(dir_path)) +#if BASE_VER < 800000 + base::DeleteFile(dir_path, false); +#else + base::DeleteFile(dir_path); +#endif + } +} + +} // namespace + bool PrefsBase::GetString(const string& key, string* value) const { return storage_->GetKey(key, value); } -bool PrefsBase::SetString(const string& key, const string& value) { +bool PrefsBase::SetString(const string& key, std::string_view value) { TEST_AND_RETURN_FALSE(storage_->SetKey(key, value)); const auto observers_for_key = observers_.find(key); if (observers_for_key != observers_.end()) { @@ -54,7 +76,7 @@ bool PrefsBase::GetInt64(const string& key, int64_t* value) const { } bool PrefsBase::SetInt64(const string& key, const int64_t value) { - return SetString(key, base::Int64ToString(value)); + return SetString(key, base::NumberToString(value)); } bool PrefsBase::GetBoolean(const string& key, bool* value) const { @@ -92,6 +114,28 @@ bool PrefsBase::Delete(const string& key) { return true; } +bool PrefsBase::Delete(const string& pref_key, const vector<string>& nss) { + // Delete pref key for platform. + bool success = Delete(pref_key); + // Delete pref key in each namespace. + for (const auto& ns : nss) { + vector<string> namespace_keys; + success = GetSubKeys(ns, &namespace_keys) && success; + for (const auto& key : namespace_keys) { + auto last_key_seperator = key.find_last_of(kKeySeparator); + if (last_key_seperator != string::npos && + pref_key == key.substr(last_key_seperator + 1)) { + success = Delete(key) && success; + } + } + } + return success; +} + +bool PrefsBase::GetSubKeys(const string& ns, vector<string>* keys) const { + return storage_->GetSubKeys(ns, keys); +} + void PrefsBase::AddObserver(const string& key, ObserverInterface* observer) { observers_[key].push_back(observer); } @@ -104,6 +148,10 @@ void PrefsBase::RemoveObserver(const string& key, ObserverInterface* observer) { observers_for_key.erase(observer_it); } +string PrefsInterface::CreateSubKey(const vector<string>& ns_and_key) { + return base::JoinString(ns_and_key, string(1, kKeySeparator)); +} + // Prefs bool Prefs::Init(const base::FilePath& prefs_dir) { @@ -112,6 +160,8 @@ bool Prefs::Init(const base::FilePath& prefs_dir) { bool Prefs::FileStorage::Init(const base::FilePath& prefs_dir) { prefs_dir_ = prefs_dir; + // Delete empty directories. Ignore errors when deleting empty directories. + DeleteEmptyDirectories(prefs_dir_); return true; } @@ -119,13 +169,30 @@ bool Prefs::FileStorage::GetKey(const string& key, string* value) const { base::FilePath filename; TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename)); if (!base::ReadFileToString(filename, value)) { - LOG(INFO) << key << " not present in " << prefs_dir_.value(); return false; } return true; } -bool Prefs::FileStorage::SetKey(const string& key, const string& value) { +bool Prefs::FileStorage::GetSubKeys(const string& ns, + vector<string>* keys) const { + base::FilePath filename; + TEST_AND_RETURN_FALSE(GetFileNameForKey(ns, &filename)); + base::FileEnumerator namespace_enum( + prefs_dir_, true, base::FileEnumerator::FILES); + for (base::FilePath f = namespace_enum.Next(); !f.empty(); + f = namespace_enum.Next()) { + auto filename_str = filename.value(); + if (f.value().compare(0, filename_str.length(), filename_str) == 0) { + // Only return the key portion excluding the |prefs_dir_| with slash. + keys->push_back(f.value().substr( + prefs_dir_.AsEndingWithSeparator().value().length())); + } + } + return true; +} + +bool Prefs::FileStorage::SetKey(const string& key, std::string_view value) { base::FilePath filename; TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename)); if (!base::DirectoryExists(filename.DirName())) { @@ -147,19 +214,21 @@ bool Prefs::FileStorage::KeyExists(const string& key) const { bool Prefs::FileStorage::DeleteKey(const string& key) { base::FilePath filename; TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename)); +#if BASE_VER < 800000 TEST_AND_RETURN_FALSE(base::DeleteFile(filename, false)); +#else + TEST_AND_RETURN_FALSE(base::DeleteFile(filename)); +#endif return true; } bool Prefs::FileStorage::GetFileNameForKey(const string& key, base::FilePath* filename) const { - // Allows only non-empty keys containing [A-Za-z0-9_-]. + // Allows only non-empty keys containing [A-Za-z0-9_-/]. TEST_AND_RETURN_FALSE(!key.empty()); - for (size_t i = 0; i < key.size(); ++i) { - char c = key.at(i); + for (char c : key) TEST_AND_RETURN_FALSE(base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) || - c == '_' || c == '-'); - } + c == '_' || c == '-' || c == kKeySeparator); *filename = prefs_dir_.Append(key); return true; } @@ -175,8 +244,26 @@ bool MemoryPrefs::MemoryStorage::GetKey(const string& key, return true; } +bool MemoryPrefs::MemoryStorage::GetSubKeys(const string& ns, + vector<string>* keys) const { + using value_type = decltype(values_)::value_type; + using key_type = decltype(values_)::key_type; + auto lower_comp = [](const value_type& pr, const key_type& ns) { + return pr.first.substr(0, ns.length()) < ns; + }; + auto upper_comp = [](const key_type& ns, const value_type& pr) { + return ns < pr.first.substr(0, ns.length()); + }; + auto lower_it = + std::lower_bound(begin(values_), end(values_), ns, lower_comp); + auto upper_it = std::upper_bound(lower_it, end(values_), ns, upper_comp); + while (lower_it != upper_it) + keys->push_back((lower_it++)->first); + return true; +} + bool MemoryPrefs::MemoryStorage::SetKey(const string& key, - const string& value) { + std::string_view value) { values_[key] = value; return true; } @@ -187,9 +274,8 @@ bool MemoryPrefs::MemoryStorage::KeyExists(const string& key) const { bool MemoryPrefs::MemoryStorage::DeleteKey(const string& key) { auto it = values_.find(key); - if (it == values_.end()) - return false; - values_.erase(it); + if (it != values_.end()) + values_.erase(it); return true; } diff --git a/common/prefs.h b/common/prefs.h index 0116454f..93477ddc 100644 --- a/common/prefs.h +++ b/common/prefs.h @@ -19,6 +19,7 @@ #include <map> #include <string> +#include <string_view> #include <vector> #include <base/files/file_path.h> @@ -42,9 +43,14 @@ class PrefsBase : public PrefsInterface { // Returns whether the operation succeeded. virtual bool GetKey(const std::string& key, std::string* value) const = 0; + // Get the keys stored within the namespace. If there are no keys in the + // namespace, |keys| will be empty. Returns whether the operation succeeded. + virtual bool GetSubKeys(const std::string& ns, + std::vector<std::string>* keys) const = 0; + // Set the value of the key named |key| to |value| regardless of the // previous value. Returns whether the operation succeeded. - virtual bool SetKey(const std::string& key, const std::string& value) = 0; + virtual bool SetKey(const std::string& key, std::string_view value) = 0; // Returns whether the key named |key| exists. virtual bool KeyExists(const std::string& key) const = 0; @@ -61,7 +67,7 @@ class PrefsBase : public PrefsInterface { // PrefsInterface methods. bool GetString(const std::string& key, std::string* value) const override; - bool SetString(const std::string& key, const std::string& value) override; + bool SetString(const std::string& key, std::string_view value) override; bool GetInt64(const std::string& key, int64_t* value) const override; bool SetInt64(const std::string& key, const int64_t value) override; bool GetBoolean(const std::string& key, bool* value) const override; @@ -69,6 +75,11 @@ class PrefsBase : public PrefsInterface { bool Exists(const std::string& key) const override; bool Delete(const std::string& key) override; + bool Delete(const std::string& pref_key, + const std::vector<std::string>& nss) override; + + bool GetSubKeys(const std::string& ns, + std::vector<std::string>* keys) const override; void AddObserver(const std::string& key, ObserverInterface* observer) override; @@ -111,7 +122,9 @@ class Prefs : public PrefsBase { // PrefsBase::StorageInterface overrides. bool GetKey(const std::string& key, std::string* value) const override; - bool SetKey(const std::string& key, const std::string& value) override; + bool GetSubKeys(const std::string& ns, + std::vector<std::string>* keys) const override; + bool SetKey(const std::string& key, std::string_view value) override; bool KeyExists(const std::string& key) const override; bool DeleteKey(const std::string& key) override; @@ -149,7 +162,9 @@ class MemoryPrefs : public PrefsBase { // PrefsBase::StorageInterface overrides. bool GetKey(const std::string& key, std::string* value) const override; - bool SetKey(const std::string& key, const std::string& value) override; + bool GetSubKeys(const std::string& ns, + std::vector<std::string>* keys) const override; + bool SetKey(const std::string& key, std::string_view value) override; bool KeyExists(const std::string& key) const override; bool DeleteKey(const std::string& key) override; diff --git a/common/prefs_interface.h b/common/prefs_interface.h index 03ae3ecd..e773a35e 100644 --- a/common/prefs_interface.h +++ b/common/prefs_interface.h @@ -20,6 +20,7 @@ #include <stdint.h> #include <string> +#include <vector> namespace chromeos_update_engine { @@ -51,7 +52,7 @@ class PrefsInterface { // Associates |key| with a string |value|. Returns true on success, // false otherwise. - virtual bool SetString(const std::string& key, const std::string& value) = 0; + virtual bool SetString(const std::string& key, std::string_view value) = 0; // Gets an int64_t |value| associated with |key|. Returns true on // success, false on failure (including when the |key| is not @@ -79,6 +80,19 @@ class PrefsInterface { // this key. Calling with non-existent keys does nothing. virtual bool Delete(const std::string& key) = 0; + // Deletes the pref key from platform and given namespace subdirectories. + // Keys are matched against end of pref keys in each namespace. + // Returns true if all deletes were successful. + virtual bool Delete(const std::string& pref_key, + const std::vector<std::string>& nss) = 0; + + // Creates a key which is part of a sub preference. + static std::string CreateSubKey(const std::vector<std::string>& ns_with_key); + + // Returns a list of keys within the namespace. + virtual bool GetSubKeys(const std::string& ns, + std::vector<std::string>* keys) const = 0; + // Add an observer to watch whenever the given |key| is modified. The // OnPrefSet() and OnPrefDelete() methods will be called whenever any of the // Set*() methods or the Delete() method are called on the given key, @@ -90,6 +104,10 @@ class PrefsInterface { // anymore for future Set*() and Delete() method calls. virtual void RemoveObserver(const std::string& key, ObserverInterface* observer) = 0; + + protected: + // Key separator used to create sub key and get file names, + static const char kKeySeparator = '/'; }; } // namespace chromeos_update_engine diff --git a/common/prefs_unittest.cc b/common/prefs_unittest.cc index cb6fc709..a5f46e53 100644 --- a/common/prefs_unittest.cc +++ b/common/prefs_unittest.cc @@ -20,6 +20,7 @@ #include <limits> #include <string> +#include <vector> #include <base/files/file_util.h> #include <base/files/scoped_temp_dir.h> @@ -30,8 +31,11 @@ #include <gtest/gtest.h> using std::string; +using std::vector; using testing::_; +using testing::ElementsAre; using testing::Eq; +using testing::UnorderedElementsAre; namespace { // Test key used along the tests. @@ -40,12 +44,109 @@ const char kKey[] = "test-key"; namespace chromeos_update_engine { -class PrefsTest : public ::testing::Test { +class BasePrefsTest : public ::testing::Test { + protected: + void MultiNamespaceKeyTest() { + ASSERT_TRUE(common_prefs_); + auto key0 = common_prefs_->CreateSubKey({"ns1", "key"}); + // Corner case for "ns1". + auto key0corner = common_prefs_->CreateSubKey({"ns11", "key"}); + auto key1A = common_prefs_->CreateSubKey({"ns1", "nsA", "keyA"}); + auto key1B = common_prefs_->CreateSubKey({"ns1", "nsA", "keyB"}); + auto key2 = common_prefs_->CreateSubKey({"ns1", "nsB", "key"}); + // Corner case for "ns1/nsB". + auto key2corner = common_prefs_->CreateSubKey({"ns1", "nsB1", "key"}); + EXPECT_FALSE(common_prefs_->Exists(key0)); + EXPECT_FALSE(common_prefs_->Exists(key1A)); + EXPECT_FALSE(common_prefs_->Exists(key1B)); + EXPECT_FALSE(common_prefs_->Exists(key2)); + + EXPECT_TRUE(common_prefs_->SetString(key0, "")); + EXPECT_TRUE(common_prefs_->SetString(key0corner, "")); + EXPECT_TRUE(common_prefs_->SetString(key1A, "")); + EXPECT_TRUE(common_prefs_->SetString(key1B, "")); + EXPECT_TRUE(common_prefs_->SetString(key2, "")); + EXPECT_TRUE(common_prefs_->SetString(key2corner, "")); + + EXPECT_TRUE(common_prefs_->Exists(key0)); + EXPECT_TRUE(common_prefs_->Exists(key0corner)); + EXPECT_TRUE(common_prefs_->Exists(key1A)); + EXPECT_TRUE(common_prefs_->Exists(key1B)); + EXPECT_TRUE(common_prefs_->Exists(key2)); + EXPECT_TRUE(common_prefs_->Exists(key2corner)); + + vector<string> keys2; + EXPECT_TRUE(common_prefs_->GetSubKeys("ns1/nsB/", &keys2)); + EXPECT_THAT(keys2, ElementsAre(key2)); + for (const auto& key : keys2) + EXPECT_TRUE(common_prefs_->Delete(key)); + EXPECT_TRUE(common_prefs_->Exists(key0)); + EXPECT_TRUE(common_prefs_->Exists(key0corner)); + EXPECT_TRUE(common_prefs_->Exists(key1A)); + EXPECT_TRUE(common_prefs_->Exists(key1B)); + EXPECT_FALSE(common_prefs_->Exists(key2)); + EXPECT_TRUE(common_prefs_->Exists(key2corner)); + + vector<string> keys2corner; + EXPECT_TRUE(common_prefs_->GetSubKeys("ns1/nsB", &keys2corner)); + EXPECT_THAT(keys2corner, ElementsAre(key2corner)); + for (const auto& key : keys2corner) + EXPECT_TRUE(common_prefs_->Delete(key)); + EXPECT_FALSE(common_prefs_->Exists(key2corner)); + + vector<string> keys1; + EXPECT_TRUE(common_prefs_->GetSubKeys("ns1/nsA/", &keys1)); + EXPECT_THAT(keys1, UnorderedElementsAre(key1A, key1B)); + for (const auto& key : keys1) + EXPECT_TRUE(common_prefs_->Delete(key)); + EXPECT_TRUE(common_prefs_->Exists(key0)); + EXPECT_TRUE(common_prefs_->Exists(key0corner)); + EXPECT_FALSE(common_prefs_->Exists(key1A)); + EXPECT_FALSE(common_prefs_->Exists(key1B)); + + vector<string> keys0; + EXPECT_TRUE(common_prefs_->GetSubKeys("ns1/", &keys0)); + EXPECT_THAT(keys0, ElementsAre(key0)); + for (const auto& key : keys0) + EXPECT_TRUE(common_prefs_->Delete(key)); + EXPECT_FALSE(common_prefs_->Exists(key0)); + EXPECT_TRUE(common_prefs_->Exists(key0corner)); + + vector<string> keys0corner; + EXPECT_TRUE(common_prefs_->GetSubKeys("ns1", &keys0corner)); + EXPECT_THAT(keys0corner, ElementsAre(key0corner)); + for (const auto& key : keys0corner) + EXPECT_TRUE(common_prefs_->Delete(key)); + EXPECT_FALSE(common_prefs_->Exists(key0corner)); + + // Test sub directory namespace. + const string kDlcPrefsSubDir = "foo-dir"; + key1A = common_prefs_->CreateSubKey({kDlcPrefsSubDir, "dlc1", "keyA"}); + EXPECT_TRUE(common_prefs_->SetString(key1A, "fp_1A")); + key1B = common_prefs_->CreateSubKey({kDlcPrefsSubDir, "dlc1", "keyB"}); + EXPECT_TRUE(common_prefs_->SetString(key1B, "fp_1B")); + auto key2A = common_prefs_->CreateSubKey({kDlcPrefsSubDir, "dlc2", "keyA"}); + EXPECT_TRUE(common_prefs_->SetString(key2A, "fp_A2")); + + vector<string> fpKeys; + EXPECT_TRUE(common_prefs_->GetSubKeys(kDlcPrefsSubDir, &fpKeys)); + EXPECT_EQ(fpKeys.size(), 3UL); + EXPECT_TRUE(common_prefs_->Delete(fpKeys[0])); + EXPECT_TRUE(common_prefs_->Delete(fpKeys[1])); + EXPECT_TRUE(common_prefs_->Delete(fpKeys[2])); + EXPECT_FALSE(common_prefs_->Exists(key1A)); + } + + PrefsInterface* common_prefs_; +}; + +class PrefsTest : public BasePrefsTest { protected: void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); prefs_dir_ = temp_dir_.GetPath(); ASSERT_TRUE(prefs_.Init(prefs_dir_)); + common_prefs_ = &prefs_; } bool SetValue(const string& key, const string& value) { @@ -59,6 +160,31 @@ class PrefsTest : public ::testing::Test { Prefs prefs_; }; +TEST(Prefs, Init) { + Prefs prefs; + const string ns1 = "ns1"; + const string ns2A = "ns2A"; + const string ns2B = "ns2B"; + const string sub_pref = "sp"; + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + auto ns1_path = temp_dir.GetPath().Append(ns1); + auto ns2A_path = ns1_path.Append(ns2A); + auto ns2B_path = ns1_path.Append(ns2B); + auto sub_pref_path = ns2A_path.Append(sub_pref); + + EXPECT_TRUE(base::CreateDirectory(ns2B_path)); + EXPECT_TRUE(base::PathExists(ns2B_path)); + + EXPECT_TRUE(base::CreateDirectory(sub_pref_path)); + EXPECT_TRUE(base::PathExists(sub_pref_path)); + + EXPECT_TRUE(base::PathExists(ns1_path)); + ASSERT_TRUE(prefs.Init(temp_dir.GetPath())); + EXPECT_FALSE(base::PathExists(ns1_path)); +} + TEST_F(PrefsTest, GetFileNameForKey) { const char kAllvalidCharsKey[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-"; @@ -77,6 +203,18 @@ TEST_F(PrefsTest, GetFileNameForKeyEmpty) { EXPECT_FALSE(prefs_.file_storage_.GetFileNameForKey("", &path)); } +TEST_F(PrefsTest, CreateSubKey) { + const string name_space = "ns"; + const string sub_pref1 = "sp1"; + const string sub_pref2 = "sp2"; + const string sub_key = "sk"; + + EXPECT_EQ(PrefsInterface::CreateSubKey({name_space, sub_pref1, sub_key}), + "ns/sp1/sk"); + EXPECT_EQ(PrefsInterface::CreateSubKey({name_space, sub_pref2, sub_key}), + "ns/sp2/sk"); +} + TEST_F(PrefsTest, GetString) { const string test_data = "test data"; ASSERT_TRUE(SetValue(kKey, test_data)); @@ -279,6 +417,94 @@ TEST_F(PrefsTest, DeleteWorks) { EXPECT_FALSE(prefs_.Exists(kKey)); } +TEST_F(PrefsTest, SetDeleteSubKey) { + const string name_space = "ns"; + const string sub_pref = "sp"; + const string sub_key1 = "sk1"; + const string sub_key2 = "sk2"; + auto key1 = prefs_.CreateSubKey({name_space, sub_pref, sub_key1}); + auto key2 = prefs_.CreateSubKey({name_space, sub_pref, sub_key2}); + base::FilePath sub_pref_path = prefs_dir_.Append(name_space).Append(sub_pref); + + ASSERT_TRUE(prefs_.SetInt64(key1, 0)); + ASSERT_TRUE(prefs_.SetInt64(key2, 0)); + EXPECT_TRUE(base::PathExists(sub_pref_path.Append(sub_key1))); + EXPECT_TRUE(base::PathExists(sub_pref_path.Append(sub_key2))); + + ASSERT_TRUE(prefs_.Delete(key1)); + EXPECT_FALSE(base::PathExists(sub_pref_path.Append(sub_key1))); + EXPECT_TRUE(base::PathExists(sub_pref_path.Append(sub_key2))); + ASSERT_TRUE(prefs_.Delete(key2)); + EXPECT_FALSE(base::PathExists(sub_pref_path.Append(sub_key2))); + prefs_.Init(prefs_dir_); + EXPECT_FALSE(base::PathExists(prefs_dir_.Append(name_space))); +} + +TEST_F(PrefsTest, DeletePrefs) { + const string kPrefsSubDir = "foo-dir"; + const string kFpKey = "kPrefFp"; + const string kNotFpKey = "NotkPrefFp"; + const string kOtherKey = "kPrefNotFp"; + + EXPECT_TRUE(prefs_.SetString(kFpKey, "3.000")); + EXPECT_TRUE(prefs_.SetString(kOtherKey, "not_fp_val")); + + auto key1_fp = prefs_.CreateSubKey({kPrefsSubDir, "id-1", kFpKey}); + EXPECT_TRUE(prefs_.SetString(key1_fp, "3.7")); + auto key_not_fp = prefs_.CreateSubKey({kPrefsSubDir, "id-1", kOtherKey}); + EXPECT_TRUE(prefs_.SetString(key_not_fp, "not_fp_val")); + auto key2_fp = prefs_.CreateSubKey({kPrefsSubDir, "id-2", kFpKey}); + EXPECT_TRUE(prefs_.SetString(key2_fp, "3.9")); + auto key3_fp = prefs_.CreateSubKey({kPrefsSubDir, "id-3", kFpKey}); + EXPECT_TRUE(prefs_.SetString(key3_fp, "3.45")); + + // Pref key does not match full subkey at end, should not delete. + auto key_middle_fp = prefs_.CreateSubKey({kPrefsSubDir, kFpKey, kOtherKey}); + EXPECT_TRUE(prefs_.SetString(key_middle_fp, "not_fp_val")); + auto key_end_not_fp = prefs_.CreateSubKey({kPrefsSubDir, "id-1", kNotFpKey}); + EXPECT_TRUE(prefs_.SetString(key_end_not_fp, "not_fp_val")); + + // Delete key in platform and one namespace. + prefs_.Delete(kFpKey, {kPrefsSubDir}); + + EXPECT_FALSE(prefs_.Exists(kFpKey)); + EXPECT_FALSE(prefs_.Exists(key1_fp)); + EXPECT_FALSE(prefs_.Exists(key2_fp)); + EXPECT_FALSE(prefs_.Exists(key3_fp)); + + // Check other keys are not deleted. + EXPECT_TRUE(prefs_.Exists(kOtherKey)); + EXPECT_TRUE(prefs_.Exists(key_not_fp)); + EXPECT_TRUE(prefs_.Exists(key_middle_fp)); + EXPECT_TRUE(prefs_.Exists(key_end_not_fp)); +} + +TEST_F(PrefsTest, DeleteMultipleNamespaces) { + const string kFirstSubDir = "foo-dir"; + const string kSecondarySubDir = "bar-dir"; + const string kTertiarySubDir = "ter-dir"; + const string kFpKey = "kPrefFp"; + + EXPECT_TRUE(prefs_.SetString(kFpKey, "3.000")); + // Set pref key in different namespaces. + auto key1_fp = prefs_.CreateSubKey({kFirstSubDir, "id-1", kFpKey}); + EXPECT_TRUE(prefs_.SetString(key1_fp, "3.7")); + auto key2_fp = prefs_.CreateSubKey({kSecondarySubDir, "id-3", kFpKey}); + EXPECT_TRUE(prefs_.SetString(key2_fp, "7.45")); + auto key3_fp = prefs_.CreateSubKey({kTertiarySubDir, "id-3", kFpKey}); + EXPECT_TRUE(prefs_.SetString(key3_fp, "7.45")); + + // Delete key in platform and given namespaces. + prefs_.Delete(kFpKey, {kFirstSubDir, kSecondarySubDir}); + + EXPECT_FALSE(prefs_.Exists(kFpKey)); + EXPECT_FALSE(prefs_.Exists(key1_fp)); + EXPECT_FALSE(prefs_.Exists(key2_fp)); + + // Tertiary namespace not given to delete. Key should still exist. + EXPECT_TRUE(prefs_.Exists(key3_fp)); +} + class MockPrefsObserver : public PrefsInterface::ObserverInterface { public: MOCK_METHOD1(OnPrefSet, void(const string&)); @@ -299,6 +525,19 @@ TEST_F(PrefsTest, ObserversCalled) { prefs_.Delete(kKey); testing::Mock::VerifyAndClearExpectations(&mock_obserser); + auto key1 = prefs_.CreateSubKey({"ns", "sp1", "key1"}); + prefs_.AddObserver(key1, &mock_obserser); + + EXPECT_CALL(mock_obserser, OnPrefSet(key1)); + EXPECT_CALL(mock_obserser, OnPrefDeleted(_)).Times(0); + prefs_.SetString(key1, "value"); + testing::Mock::VerifyAndClearExpectations(&mock_obserser); + + EXPECT_CALL(mock_obserser, OnPrefSet(_)).Times(0); + EXPECT_CALL(mock_obserser, OnPrefDeleted(Eq(key1))); + prefs_.Delete(key1); + testing::Mock::VerifyAndClearExpectations(&mock_obserser); + prefs_.RemoveObserver(kKey, &mock_obserser); } @@ -341,8 +580,14 @@ TEST_F(PrefsTest, UnsuccessfulCallsNotObserved) { prefs_.RemoveObserver(kInvalidKey, &mock_obserser); } -class MemoryPrefsTest : public ::testing::Test { +TEST_F(PrefsTest, MultiNamespaceKeyTest) { + MultiNamespaceKeyTest(); +} + +class MemoryPrefsTest : public BasePrefsTest { protected: + void SetUp() override { common_prefs_ = &prefs_; } + MemoryPrefs prefs_; }; @@ -358,7 +603,16 @@ TEST_F(MemoryPrefsTest, BasicTest) { EXPECT_TRUE(prefs_.Delete(kKey)); EXPECT_FALSE(prefs_.Exists(kKey)); - EXPECT_FALSE(prefs_.Delete(kKey)); + EXPECT_TRUE(prefs_.Delete(kKey)); + + auto key = prefs_.CreateSubKey({"ns", "sp", "sk"}); + ASSERT_TRUE(prefs_.SetInt64(key, 0)); + EXPECT_TRUE(prefs_.Exists(key)); + EXPECT_TRUE(prefs_.Delete(kKey)); +} + +TEST_F(MemoryPrefsTest, MultiNamespaceKeyTest) { + MultiNamespaceKeyTest(); } } // namespace chromeos_update_engine diff --git a/common/scoped_task_id.h b/common/scoped_task_id.h new file mode 100644 index 00000000..91a29860 --- /dev/null +++ b/common/scoped_task_id.h @@ -0,0 +1,123 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_SCOPED_TASK_ID_H_ +#define UPDATE_ENGINE_SCOPED_TASK_ID_H_ + +#include <type_traits> +#include <utility> + +#include <base/bind.h> +#include <brillo/message_loops/message_loop.h> + +namespace chromeos_update_engine { + +// This class provides unique_ptr like semantic for |MessageLoop::TaskId|, when +// instance of this class goes out of scope, underlying task will be cancelled. +class ScopedTaskId { + using MessageLoop = brillo::MessageLoop; + + public: + // Move only type similar to unique_ptr. + ScopedTaskId(const ScopedTaskId&) = delete; + ScopedTaskId& operator=(const ScopedTaskId&) = delete; + + constexpr ScopedTaskId() = default; + + constexpr ScopedTaskId(ScopedTaskId&& other) noexcept { + *this = std::move(other); + } + + constexpr ScopedTaskId& operator=(ScopedTaskId&& other) noexcept { + std::swap(task_id_, other.task_id_); + return *this; + } + + // Post a callback on current message loop, return true if succeeded, false if + // the previous callback hasn't run yet, or scheduling failed at MessageLoop + // side. + [[nodiscard]] bool PostTask(const base::Location& from_here, + base::OnceClosure&& callback, + base::TimeDelta delay = {}) noexcept { + return PostTask<decltype(callback)>(from_here, std::move(callback), delay); + } + [[nodiscard]] bool PostTask(const base::Location& from_here, + std::function<void()>&& callback, + base::TimeDelta delay = {}) noexcept { + return PostTask<decltype(callback)>(from_here, std::move(callback), delay); + } + + ~ScopedTaskId() noexcept { Cancel(); } + + // Cancel the underlying managed task, true if cancel successful. False if no + // task scheduled or task cancellation failed + bool Cancel() noexcept { + if (task_id_ != MessageLoop::kTaskIdNull) { + if (MessageLoop::current()->CancelTask(task_id_)) { + LOG(INFO) << "Cancelled task id " << task_id_; + task_id_ = MessageLoop::kTaskIdNull; + return true; + } + } + return false; + } + + [[nodiscard]] constexpr bool IsScheduled() const noexcept { + return task_id_ != MessageLoop::kTaskIdNull; + } + + [[nodiscard]] constexpr bool operator==(const ScopedTaskId& other) const + noexcept { + return other.task_id_ == task_id_; + } + + [[nodiscard]] constexpr bool operator<(const ScopedTaskId& other) const + noexcept { + return task_id_ < other.task_id_; + } + + private: + template <typename Callable> + [[nodiscard]] bool PostTask(const base::Location& from_here, + Callable&& callback, + base::TimeDelta delay) noexcept { + if (task_id_ != MessageLoop::kTaskIdNull) { + LOG(ERROR) << "Scheduling another task but task id " << task_id_ + << " isn't executed yet! This can cause the old task to leak."; + return false; + } + task_id_ = MessageLoop::current()->PostDelayedTask( + from_here, + base::BindOnce(&ScopedTaskId::ExecuteTask<decltype(callback)>, + base::Unretained(this), + std::move(callback)), + delay); + return task_id_ != MessageLoop::kTaskIdNull; + } + template <typename Callable> + void ExecuteTask(Callable&& callback) { + task_id_ = MessageLoop::kTaskIdNull; + if constexpr (std::is_same_v<Callable&&, base::OnceClosure&&>) { + std::move(callback).Run(); + } else { + std::move(callback)(); + } + } + MessageLoop::TaskId task_id_{MessageLoop::kTaskIdNull}; +}; +} // namespace chromeos_update_engine + +#endif diff --git a/service_observer_interface.h b/common/service_observer_interface.h index 4edb0ac2..c4712314 100644 --- a/service_observer_interface.h +++ b/common/service_observer_interface.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_SERVICE_OBSERVER_INTERFACE_H_ -#define UPDATE_ENGINE_SERVICE_OBSERVER_INTERFACE_H_ +#ifndef UPDATE_ENGINE_COMMON_SERVICE_OBSERVER_INTERFACE_H_ +#define UPDATE_ENGINE_COMMON_SERVICE_OBSERVER_INTERFACE_H_ #include <memory> #include <string> @@ -43,4 +43,4 @@ class ServiceObserverInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_SERVICE_OBSERVER_INTERFACE_H_ +#endif // UPDATE_ENGINE_COMMON_SERVICE_OBSERVER_INTERFACE_H_ diff --git a/common/subprocess.cc b/common/subprocess.cc index 0131f10f..023017b9 100644 --- a/common/subprocess.cc +++ b/common/subprocess.cc @@ -29,9 +29,9 @@ #include <base/bind.h> #include <base/logging.h> #include <base/posix/eintr_wrapper.h> +#include <base/stl_util.h> #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> -#include <brillo/process.h> #include <brillo/secure_blob.h> #include "update_engine/common/utils.h" @@ -95,6 +95,7 @@ bool LaunchProcess(const vector<string>& cmd, proc->RedirectUsingPipe(STDOUT_FILENO, false); proc->SetPreExecCallback(base::Bind(&SetupChild, env, flags)); + LOG(INFO) << "Running \"" << base::JoinString(cmd, " ") << "\""; return proc->Start(); } @@ -122,13 +123,12 @@ void Subprocess::OnStdoutReady(SubprocessRecord* record) { bytes_read = 0; bool eof; bool ok = utils::ReadAll( - record->stdout_fd, buf, arraysize(buf), &bytes_read, &eof); + record->stdout_fd, buf, base::size(buf), &bytes_read, &eof); record->stdout.append(buf, bytes_read); if (!ok || eof) { // There was either an error or an EOF condition, so we are done watching // the file descriptor. - MessageLoop::current()->CancelTask(record->stdout_task_id); - record->stdout_task_id = MessageLoop::kTaskIdNull; + record->stdout_controller.reset(); return; } } while (bytes_read); @@ -143,8 +143,7 @@ void Subprocess::ChildExitedCallback(const siginfo_t& info) { // Make sure we read any remaining process output and then close the pipe. OnStdoutReady(record); - MessageLoop::current()->CancelTask(record->stdout_task_id); - record->stdout_task_id = MessageLoop::kTaskIdNull; + record->stdout_controller.reset(); // Don't print any log if the subprocess exited with exit code 0. if (info.si_code != CLD_EXITED) { @@ -199,12 +198,9 @@ pid_t Subprocess::ExecFlags(const vector<string>& cmd, << record->stdout_fd << "."; } - record->stdout_task_id = MessageLoop::current()->WatchFileDescriptor( - FROM_HERE, + record->stdout_controller = base::FileDescriptorWatcher::WatchReadable( record->stdout_fd, - MessageLoop::WatchMode::kWatchRead, - true, - base::Bind(&Subprocess::OnStdoutReady, record.get())); + base::BindRepeating(&Subprocess::OnStdoutReady, record.get())); subprocess_records_[pid] = std::move(record); return pid; @@ -234,22 +230,20 @@ int Subprocess::GetPipeFd(pid_t pid, int fd) const { bool Subprocess::SynchronousExec(const vector<string>& cmd, int* return_code, - string* stdout) { - // The default for SynchronousExec is to use kSearchPath since the code relies - // on that. - return SynchronousExecFlags( - cmd, kRedirectStderrToStdout | kSearchPath, return_code, stdout); + string* stdout, + string* stderr) { + // The default for |SynchronousExec| is to use |kSearchPath| since the code + // relies on that. + return SynchronousExecFlags(cmd, kSearchPath, return_code, stdout, stderr); } bool Subprocess::SynchronousExecFlags(const vector<string>& cmd, uint32_t flags, int* return_code, - string* stdout) { + string* stdout, + string* stderr) { brillo::ProcessImpl proc; - // It doesn't make sense to redirect some pipes in the synchronous case - // because we won't be reading on our end, so we don't expose the output_pipes - // in this case. - if (!LaunchProcess(cmd, flags, {}, &proc)) { + if (!LaunchProcess(cmd, flags, {STDERR_FILENO}, &proc)) { LOG(ERROR) << "Failed to launch subprocess"; return false; } @@ -257,21 +251,39 @@ bool Subprocess::SynchronousExecFlags(const vector<string>& cmd, if (stdout) { stdout->clear(); } + if (stderr) { + stderr->clear(); + } - int fd = proc.GetPipe(STDOUT_FILENO); + // Read from both stdout and stderr individually. + int stdout_fd = proc.GetPipe(STDOUT_FILENO); + int stderr_fd = proc.GetPipe(STDERR_FILENO); vector<char> buffer(32 * 1024); - while (true) { - int rc = HANDLE_EINTR(read(fd, buffer.data(), buffer.size())); - if (rc < 0) { - PLOG(ERROR) << "Reading from child's output"; - break; - } else if (rc == 0) { - break; - } else { - if (stdout) + bool stdout_closed = false, stderr_closed = false; + while (!stdout_closed || !stderr_closed) { + if (!stdout_closed) { + int rc = HANDLE_EINTR(read(stdout_fd, buffer.data(), buffer.size())); + if (rc <= 0) { + stdout_closed = true; + if (rc < 0) + PLOG(ERROR) << "Reading from child's stdout"; + } else if (stdout != nullptr) { stdout->append(buffer.data(), rc); + } + } + + if (!stderr_closed) { + int rc = HANDLE_EINTR(read(stderr_fd, buffer.data(), buffer.size())); + if (rc <= 0) { + stderr_closed = true; + if (rc < 0) + PLOG(ERROR) << "Reading from child's stderr"; + } else if (stderr != nullptr) { + stderr->append(buffer.data(), rc); + } } } + // At this point, the subprocess already closed the output, so we only need to // wait for it to finish. int proc_return_code = proc.Wait(); diff --git a/common/subprocess.h b/common/subprocess.h index bc19d16f..2ed8b81f 100644 --- a/common/subprocess.h +++ b/common/subprocess.h @@ -25,13 +25,19 @@ #include <vector> #include <base/callback.h> +#include <base/files/file_descriptor_watcher_posix.h> #include <base/logging.h> #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> -#include <gtest/gtest_prod.h> // for FRIEND_TEST +#endif // __CHROMEOS__ +#include <gtest/gtest_prod.h> // The Subprocess class is a singleton. It's used to spawn off a subprocess // and get notified when the subprocess exits. The result of Exec() can @@ -87,14 +93,16 @@ class Subprocess { // Executes a command synchronously. Returns true on success. If |stdout| is // non-null, the process output is stored in it, otherwise the output is - // logged. Note that stderr is redirected to stdout. + // logged. static bool SynchronousExec(const std::vector<std::string>& cmd, int* return_code, - std::string* stdout); + std::string* stdout, + std::string* stderr); static bool SynchronousExecFlags(const std::vector<std::string>& cmd, uint32_t flags, int* return_code, - std::string* stdout); + std::string* stdout, + std::string* stderr); // Gets the one instance. static Subprocess& Get() { return *subprocess_singleton_; } @@ -120,8 +128,8 @@ class Subprocess { // These are used to monitor the stdout of the running process, including // the stderr if it was redirected. - brillo::MessageLoop::TaskId stdout_task_id{ - brillo::MessageLoop::kTaskIdNull}; + 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 104ef419..ff4158e8 100644 --- a/common/subprocess_unittest.cc +++ b/common/subprocess_unittest.cc @@ -28,9 +28,14 @@ #include <base/bind.h> #include <base/files/scoped_temp_dir.h> #include <base/location.h> +#if BASE_VER < 780000 // Android #include <base/message_loop/message_loop.h> +#endif // BASE_VER < 780000 #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> +#if BASE_VER >= 780000 // Chrome OS +#include <base/task/single_thread_task_executor.h> +#endif // BASE_VER >= 780000 #include <base/time/time.h> #include <brillo/message_loops/base_message_loop.h> #include <brillo/message_loops/message_loop.h> @@ -45,6 +50,7 @@ using base::TimeDelta; using brillo::MessageLoop; using std::string; +using std::unique_ptr; using std::vector; namespace { @@ -69,10 +75,16 @@ class SubprocessTest : public ::testing::Test { subprocess_.Init(&async_signal_handler_); } +#if BASE_VER < 780000 // Android base::MessageLoopForIO base_loop_; brillo::BaseMessageLoop loop_{&base_loop_}; +#else // Chrome OS + base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO}; + brillo::BaseMessageLoop loop_{base_loop_.task_runner()}; +#endif // BASE_VER < 780000 brillo::AsynchronousSignalHandler async_signal_handler_; Subprocess subprocess_; + unique_ptr<base::FileDescriptorWatcher::Controller> watcher_; }; namespace { @@ -193,7 +205,7 @@ TEST_F(SubprocessTest, EnvVarsAreFiltered) { TEST_F(SubprocessTest, SynchronousTrueSearchsOnPath) { int rc = -1; EXPECT_TRUE(Subprocess::SynchronousExecFlags( - {"true"}, Subprocess::kSearchPath, &rc, nullptr)); + {"true"}, Subprocess::kSearchPath, &rc, nullptr, nullptr)); EXPECT_EQ(0, rc); } @@ -201,16 +213,17 @@ TEST_F(SubprocessTest, SynchronousEchoTest) { vector<string> cmd = { kBinPath "/sh", "-c", "echo -n stdout-here; echo -n stderr-there >&2"}; int rc = -1; - string stdout; - ASSERT_TRUE(Subprocess::SynchronousExec(cmd, &rc, &stdout)); + string stdout, stderr; + ASSERT_TRUE(Subprocess::SynchronousExec(cmd, &rc, &stdout, &stderr)); EXPECT_EQ(0, rc); - EXPECT_EQ("stdout-herestderr-there", stdout); + EXPECT_EQ("stdout-here", stdout); + EXPECT_EQ("stderr-there", stderr); } TEST_F(SubprocessTest, SynchronousEchoNoOutputTest) { int rc = -1; ASSERT_TRUE(Subprocess::SynchronousExec( - {kBinPath "/sh", "-c", "echo test"}, &rc, nullptr)); + {kBinPath "/sh", "-c", "echo test"}, &rc, nullptr, nullptr)); EXPECT_EQ(0, rc); } @@ -255,26 +268,28 @@ TEST_F(SubprocessTest, CancelTest) { int fifo_fd = HANDLE_EINTR(open(fifo_path.c_str(), O_RDONLY)); EXPECT_GE(fifo_fd, 0); - loop_.WatchFileDescriptor(FROM_HERE, - fifo_fd, - MessageLoop::WatchMode::kWatchRead, - false, - base::Bind( - [](int fifo_fd, uint32_t tag) { - char c; - EXPECT_EQ(1, - HANDLE_EINTR(read(fifo_fd, &c, 1))); - EXPECT_EQ('X', c); - LOG(INFO) << "Killing tag " << tag; - Subprocess::Get().KillExec(tag); - }, - fifo_fd, - tag)); + watcher_ = base::FileDescriptorWatcher::WatchReadable( + fifo_fd, + base::Bind( + [](unique_ptr<base::FileDescriptorWatcher::Controller>* watcher, + int fifo_fd, + uint32_t tag) { + char c; + EXPECT_EQ(1, HANDLE_EINTR(read(fifo_fd, &c, 1))); + EXPECT_EQ('X', c); + LOG(INFO) << "Killing tag " << tag; + Subprocess::Get().KillExec(tag); + *watcher = nullptr; + }, + // watcher_ is no longer used outside the clousure. + base::Unretained(&watcher_), + fifo_fd, + tag)); // 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/payload_generator/graph_types.cc b/common/system_state.cc index c03766d0..cff1dfe8 100644 --- a/payload_generator/graph_types.cc +++ b/common/system_state.cc @@ -1,5 +1,5 @@ // -// Copyright (C) 2015 The Android Open Source Project +// 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. @@ -14,10 +14,10 @@ // limitations under the License. // -#include "update_engine/payload_generator/graph_types.h" +#include "update_engine/common/system_state.h" namespace chromeos_update_engine { -const Vertex::Index Vertex::kInvalidIndex = static_cast<Vertex::Index>(-1); +SystemState* SystemState::g_pointer_ = nullptr; } // namespace chromeos_update_engine diff --git a/system_state.h b/common/system_state.h index f46cbcf5..8a9c8655 100644 --- a/system_state.h +++ b/common/system_state.h @@ -14,8 +14,15 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_SYSTEM_STATE_H_ -#define UPDATE_ENGINE_SYSTEM_STATE_H_ +#ifndef UPDATE_ENGINE_COMMON_SYSTEM_STATE_H_ +#define UPDATE_ENGINE_COMMON_SYSTEM_STATE_H_ + +#include <memory> + +#include <base/logging.h> + +#include "update_engine/common/clock_interface.h" +#include "update_engine/common/prefs_interface.h" namespace chromeos_update_manager { @@ -35,7 +42,6 @@ namespace chromeos_update_engine { // any circular references in header file inclusion. Hence forward-declaring // the required classes. class BootControlInterface; -class ClockInterface; class ConnectionManagerInterface; class DlcServiceInterface; class HardwareInterface; @@ -44,20 +50,20 @@ class OmahaRequestParams; class P2PManager; class PayloadStateInterface; class PowerManagerInterface; -class PrefsInterface; class UpdateAttempter; // An interface to global system context, including platform resources, // the current state of the system, high-level objects whose lifetime is same // as main, system interfaces, etc. // Carved out separately so it can be mocked for unit tests. -// Currently it has only one method, but we should start migrating other -// methods to use this as and when needed to unit test them. -// TODO(jaysri): Consider renaming this to something like GlobalContext. class SystemState { public: - // Destructs this object. - virtual ~SystemState() {} + virtual ~SystemState() = default; + + static SystemState* Get() { + CHECK(g_pointer_ != nullptr); + return g_pointer_; + } // Sets or gets the latest device policy. virtual void set_device_policy(const policy::DevicePolicy* device_policy) = 0; @@ -113,8 +119,11 @@ class SystemState { // Returns a pointer to the DlcServiceInterface singleton. virtual DlcServiceInterface* dlcservice() = 0; + + protected: + static SystemState* g_pointer_; }; } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_SYSTEM_STATE_H_ +#endif // UPDATE_ENGINE_COMMON_SYSTEM_STATE_H_ diff --git a/common/test_utils.h b/common/test_utils.h index 44b7aa14..bb5a6789 100644 --- a/common/test_utils.h +++ b/common/test_utils.h @@ -78,7 +78,7 @@ std::string Readlink(const std::string& path); void FillWithData(brillo::Blob* buffer); -// Compare the value of native array for download source parameter. +// Compare the value of builtin array for download source parameter. MATCHER_P(DownloadSourceMatcher, source_array, "") { return std::equal(source_array, source_array + kNumDownloadSources, arg); } @@ -138,22 +138,6 @@ class ScopedLoopbackDeviceBinder { DISALLOW_COPY_AND_ASSIGN(ScopedLoopbackDeviceBinder); }; -class ScopedTempFile { - public: - ScopedTempFile() : ScopedTempFile("update_engine_test_temp_file.XXXXXX") {} - - explicit ScopedTempFile(const std::string& pattern) { - EXPECT_TRUE(utils::MakeTempFile(pattern, &path_, nullptr)); - unlinker_.reset(new ScopedPathUnlinker(path_)); - } - - const std::string& path() const { return path_; } - - private: - std::string path_; - std::unique_ptr<ScopedPathUnlinker> unlinker_; -}; - class ScopedLoopMounter { public: explicit ScopedLoopMounter(const std::string& file_path, diff --git a/common/utils.cc b/common/utils.cc index fc89040c..5dbb4453 100644 --- a/common/utils.cc +++ b/common/utils.cc @@ -28,6 +28,7 @@ #include <string.h> #include <sys/mount.h> #include <sys/resource.h> +#include <sys/sendfile.h> #include <sys/stat.h> #include <sys/types.h> #include <time.h> @@ -52,7 +53,6 @@ #include <base/strings/stringprintf.h> #include <brillo/data_encoding.h> -#include "update_engine/common/clock_interface.h" #include "update_engine/common/constants.h" #include "update_engine/common/platform_constants.h" #include "update_engine/common/prefs_interface.h" @@ -84,49 +84,6 @@ const int kGetFileFormatMaxHeaderSize = 32; // The path to the kernel's boot_id. const char kBootIdPath[] = "/proc/sys/kernel/random/boot_id"; -// Return true if |disk_name| is an MTD or a UBI device. Note that this test is -// simply based on the name of the device. -bool IsMtdDeviceName(const string& disk_name) { - return base::StartsWith( - disk_name, "/dev/ubi", base::CompareCase::SENSITIVE) || - base::StartsWith(disk_name, "/dev/mtd", base::CompareCase::SENSITIVE); -} - -// Return the device name for the corresponding partition on a NAND device. -// WARNING: This function returns device names that are not mountable. -string MakeNandPartitionName(int partition_num) { - switch (partition_num) { - case 2: - case 4: - case 6: { - return base::StringPrintf("/dev/mtd%d", partition_num); - } - default: { - return base::StringPrintf("/dev/ubi%d_0", partition_num); - } - } -} - -// Return the device name for the corresponding partition on a NAND device that -// may be mountable (but may not be writable). -string MakeNandPartitionNameForMount(int partition_num) { - switch (partition_num) { - case 2: - case 4: - case 6: { - return base::StringPrintf("/dev/mtd%d", partition_num); - } - case 3: - case 5: - case 7: { - return base::StringPrintf("/dev/ubiblock%d_0", partition_num); - } - default: { - return base::StringPrintf("/dev/ubi%d_0", partition_num); - } - } -} - // If |path| is absolute, or explicit relative to the current working directory, // leaves it as is. Otherwise, uses the system's temp directory, as defined by // base::GetTempDir() and prepends it to |path|. On success stores the full @@ -155,27 +112,6 @@ bool GetTempName(const string& path, base::FilePath* template_path) { namespace utils { -string ParseECVersion(string input_line) { - base::TrimWhitespaceASCII(input_line, base::TRIM_ALL, &input_line); - - // At this point we want to convert the format key=value pair from mosys to - // a vector of key value pairs. - vector<pair<string, string>> kv_pairs; - if (base::SplitStringIntoKeyValuePairs(input_line, '=', ' ', &kv_pairs)) { - for (const pair<string, string>& kv_pair : kv_pairs) { - // Finally match against the fw_verion which may have quotes. - if (kv_pair.first == "fw_version") { - string output; - // Trim any quotes. - base::TrimString(kv_pair.second, "\"", &output); - return output; - } - } - } - LOG(ERROR) << "Unable to parse fwid from ec info."; - return ""; -} - bool WriteFile(const char* path, const void* data, size_t data_len) { int fd = HANDLE_EINTR(open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600)); TEST_AND_RETURN_FALSE_ERRNO(fd >= 0); @@ -256,10 +192,10 @@ bool WriteAll(const FileDescriptorPtr& fd, const void* buf, size_t count) { return true; } -bool PWriteAll(const FileDescriptorPtr& fd, - const void* buf, - size_t count, - off_t offset) { +bool WriteAll(const FileDescriptorPtr& fd, + const void* buf, + size_t count, + off_t offset) { TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(offset, SEEK_SET) != static_cast<off_t>(-1)); return WriteAll(fd, buf, count); @@ -282,11 +218,11 @@ bool PReadAll( return true; } -bool PReadAll(const FileDescriptorPtr& fd, - void* buf, - size_t count, - off_t offset, - ssize_t* out_bytes_read) { +bool ReadAll(const FileDescriptorPtr& fd, + void* buf, + size_t count, + off_t offset, + ssize_t* out_bytes_read) { TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(offset, SEEK_SET) != static_cast<off_t>(-1)); char* c_buf = static_cast<char*>(buf); @@ -303,6 +239,31 @@ bool PReadAll(const FileDescriptorPtr& fd, return true; } +bool PReadAll(const FileDescriptorPtr& fd, + void* buf, + size_t count, + off_t offset, + ssize_t* out_bytes_read) { + auto old_off = fd->Seek(0, SEEK_CUR); + TEST_AND_RETURN_FALSE_ERRNO(old_off >= 0); + + auto success = ReadAll(fd, buf, count, offset, out_bytes_read); + TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(old_off, SEEK_SET) == old_off); + return success; +} + +bool PWriteAll(const FileDescriptorPtr& fd, + const void* buf, + size_t count, + off_t offset) { + auto old_off = fd->Seek(0, SEEK_CUR); + TEST_AND_RETURN_FALSE_ERRNO(old_off >= 0); + + auto success = WriteAll(fd, buf, count, offset); + TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(old_off, SEEK_SET) == old_off); + return success; +} + // Append |nbytes| of content from |buf| to the vector pointed to by either // |vec_p| or |str_p|. static void AppendBytes(const uint8_t* buf, @@ -474,22 +435,6 @@ bool SplitPartitionName(const string& partition_name, return false; } - size_t partition_name_len = string::npos; - if (partition_name[last_nondigit_pos] == '_') { - // NAND block devices have weird naming which could be something - // like "/dev/ubiblock2_0". We discard "_0" in such a case. - size_t prev_nondigit_pos = - partition_name.find_last_not_of("0123456789", last_nondigit_pos - 1); - if (prev_nondigit_pos == string::npos || - (prev_nondigit_pos + 1) == last_nondigit_pos) { - LOG(ERROR) << "Unable to parse partition device name: " << partition_name; - return false; - } - - partition_name_len = last_nondigit_pos - prev_nondigit_pos; - last_nondigit_pos = prev_nondigit_pos; - } - if (out_disk_name) { // Special case for MMC devices which have the following naming scheme: // mmcblk0p2 @@ -502,8 +447,7 @@ bool SplitPartitionName(const string& partition_name, } if (out_partition_num) { - string partition_str = - partition_name.substr(last_nondigit_pos + 1, partition_name_len); + string partition_str = partition_name.substr(last_nondigit_pos + 1); *out_partition_num = atoi(partition_str.c_str()); } return true; @@ -520,13 +464,6 @@ string MakePartitionName(const string& disk_name, int partition_num) { return string(); } - if (IsMtdDeviceName(disk_name)) { - // Special case for UBI block devices. - // 1. ubiblock is not writable, we need to use plain "ubi". - // 2. There is a "_0" suffix. - return MakeNandPartitionName(partition_num); - } - string partition_name = disk_name; if (isdigit(partition_name.back())) { // Special case for devices with names ending with a digit. @@ -540,17 +477,6 @@ string MakePartitionName(const string& disk_name, int partition_num) { return partition_name; } -string MakePartitionNameForMount(const string& part_name) { - if (IsMtdDeviceName(part_name)) { - int partition_num; - if (!SplitPartitionName(part_name, nullptr, &partition_num)) { - return ""; - } - return MakeNandPartitionNameForMount(partition_num); - } - return part_name; -} - string ErrnoNumberAsString(int err) { char buf[100]; buf[0] = '\0'; @@ -567,31 +493,9 @@ bool IsSymlink(const char* path) { return lstat(path, &stbuf) == 0 && S_ISLNK(stbuf.st_mode) != 0; } -bool TryAttachingUbiVolume(int volume_num, int timeout) { - const string volume_path = base::StringPrintf("/dev/ubi%d_0", volume_num); - if (FileExists(volume_path.c_str())) { - return true; - } - - int exit_code; - vector<string> cmd = {"ubiattach", - "-m", - base::StringPrintf("%d", volume_num), - "-d", - base::StringPrintf("%d", volume_num)}; - TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &exit_code, nullptr)); - TEST_AND_RETURN_FALSE(exit_code == 0); - - cmd = {"ubiblock", "--create", volume_path}; - TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &exit_code, nullptr)); - TEST_AND_RETURN_FALSE(exit_code == 0); - - while (timeout > 0 && !FileExists(volume_path.c_str())) { - sleep(1); - timeout--; - } - - return FileExists(volume_path.c_str()); +bool IsRegFile(const char* path) { + struct stat stbuf; + return lstat(path, &stbuf) == 0 && S_ISREG(stbuf.st_mode) != 0; } bool MakeTempFile(const string& base_filename_template, @@ -925,7 +829,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) { @@ -954,7 +858,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; @@ -1016,6 +920,25 @@ bool ReadExtents(const string& path, return true; } +bool GetVpdValue(string key, string* result) { + int exit_code = 0; + string value, error; + vector<string> cmd = {"vpd_get_value", key}; + if (!chromeos_update_engine::Subprocess::SynchronousExec( + cmd, &exit_code, &value, &error) || + exit_code) { + LOG(ERROR) << "Failed to get vpd key for " << value + << " with exit code: " << exit_code << " and error: " << error; + return false; + } else if (!error.empty()) { + LOG(INFO) << "vpd_get_value succeeded but with following errors: " << error; + } + + base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value); + *result = value; + return true; +} + bool GetBootId(string* boot_id) { TEST_AND_RETURN_FALSE( base::ReadFileToString(base::FilePath(kBootIdPath), boot_id)); @@ -1083,6 +1006,40 @@ string GetTimeAsString(time_t utime) { 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 c6c34f49..59f236ef 100644 --- a/common/utils.h +++ b/common/utils.h @@ -18,6 +18,7 @@ #define UPDATE_ENGINE_COMMON_UTILS_H_ #include <errno.h> +#include <sys/types.h> #include <time.h> #include <unistd.h> @@ -53,10 +54,6 @@ std::string StringVectorToString(const std::vector<std::string>& vec_str); std::string CalculateP2PFileId(const brillo::Blob& payload_hash, size_t payload_size); -// Parse the firmware version from one line of output from the -// "mosys" command. -std::string ParseECVersion(std::string input_line); - // Writes the data passed to path. The file at path will be overwritten if it // exists. Returns true on success, false otherwise. bool WriteFile(const char* path, const void* data, size_t data_len); @@ -67,6 +64,15 @@ bool WriteAll(int fd, const void* buf, size_t count); bool PWriteAll(int fd, const void* buf, size_t count, off_t offset); bool WriteAll(const FileDescriptorPtr& fd, const void* buf, size_t count); +// WriteAll writes data at specified offset, but it modifies file position. +bool WriteAll(const FileDescriptorPtr& fd, + const void* buf, + size_t count, + off_t off); + +// https://man7.org/linux/man-pages/man2/pread.2.html +// PWriteAll writes data at specified offset, but it DOES NOT modify file +// position. Behaves similar to linux' pwrite syscall. bool PWriteAll(const FileDescriptorPtr& fd, const void* buf, size_t count, @@ -85,6 +91,16 @@ bool ReadAll( bool PReadAll( int fd, void* buf, size_t count, off_t offset, ssize_t* out_bytes_read); +// Reads data at specified offset, this function does change file position. +bool ReadAll(const FileDescriptorPtr& fd, + void* buf, + size_t count, + off_t offset, + ssize_t* out_bytes_read); + +// https://man7.org/linux/man-pages/man2/pread.2.html +// Reads data at specified offset, this function DOES NOT change file position. +// Behavior is similar to linux's pread syscall. bool PReadAll(const FileDescriptorPtr& fd, void* buf, size_t count, @@ -128,10 +144,8 @@ bool FileExists(const char* path); // Returns true if |path| exists and is a symbolic link. bool IsSymlink(const char* path); -// Try attaching UBI |volume_num|. If there is any error executing required -// commands to attach the volume, this function returns false. This function -// only returns true if "/dev/ubi%d_0" becomes available in |timeout| seconds. -bool TryAttachingUbiVolume(int volume_num, int timeout); +// Return true iff |path| exists and is a regular file +bool IsRegFile(const char* path); // If |base_filename_template| is neither absolute (starts with "/") nor // explicitly relative to the current working directory (starts with "./" or @@ -163,14 +177,6 @@ bool SplitPartitionName(const std::string& partition_name, // Returns empty string when invalid parameters are passed in std::string MakePartitionName(const std::string& disk_name, int partition_num); -// Similar to "MakePartitionName" but returns a name that is suitable for -// mounting. On NAND system we can write to "/dev/ubiX_0", which is what -// MakePartitionName returns, but we cannot mount that device. To mount, we -// have to use "/dev/ubiblockX_0" for rootfs. Stateful and OEM partitions are -// mountable with "/dev/ubiX_0". The input is a partition device such as -// /dev/sda3. Return empty string on error. -std::string MakePartitionNameForMount(const std::string& part_name); - // Set the read-only attribute on the block device |device| to the value passed // in |read_only|. Return whether the operation succeeded. bool SetBlockDeviceReadOnly(const std::string& device, bool read_only); @@ -305,6 +311,10 @@ bool ReadExtents(const std::string& path, // reboot. Returns whether it succeeded getting the boot_id. bool GetBootId(std::string* boot_id); +// Gets a string value from the vpd for a given key using the `vpd_get_value` +// 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); @@ -332,6 +342,20 @@ void ParseRollbackKeyVersion(const std::string& raw_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 @@ -369,6 +393,48 @@ class ScopedPathUnlinker { DISALLOW_COPY_AND_ASSIGN(ScopedPathUnlinker); }; +class ScopedTempFile { + public: + ScopedTempFile() : ScopedTempFile("update_engine_temp.XXXXXX") {} + + // If |open_fd| is true, a writable file descriptor will be opened for this + // file. + // If |truncate_size| is non-zero, truncate file to that size on creation. + explicit ScopedTempFile(const std::string& pattern, + bool open_fd = false, + size_t truncate_size = 0) { + CHECK(utils::MakeTempFile(pattern, &path_, open_fd ? &fd_ : nullptr)); + unlinker_.reset(new ScopedPathUnlinker(path_)); + if (open_fd) { + CHECK_GE(fd_, 0); + fd_closer_.reset(new ScopedFdCloser(&fd_)); + } + if (truncate_size > 0) { + CHECK_EQ(0, truncate(path_.c_str(), truncate_size)); + } + } + virtual ~ScopedTempFile() = default; + + const std::string& path() const { return path_; } + int fd() const { + CHECK(fd_closer_); + return fd_; + } + void CloseFd() { + CHECK(fd_closer_); + fd_closer_.reset(); + } + + private: + std::string path_; + std::unique_ptr<ScopedPathUnlinker> unlinker_; + + int fd_{-1}; + std::unique_ptr<ScopedFdCloser> fd_closer_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTempFile); +}; + // A little object to call ActionComplete on the ActionProcessor when // it's destructed. class ScopedActionCompleter { diff --git a/common/utils_unittest.cc b/common/utils_unittest.cc index b4ac2f53..20c6b849 100644 --- a/common/utils_unittest.cc +++ b/common/utils_unittest.cc @@ -41,25 +41,12 @@ namespace chromeos_update_engine { class UtilsTest : public ::testing::Test {}; -TEST(UtilsTest, CanParseECVersion) { - // Should be able to parse and valid key value line. - EXPECT_EQ("12345", utils::ParseECVersion("fw_version=12345")); - EXPECT_EQ("123456", - utils::ParseECVersion("b=1231a fw_version=123456 a=fasd2")); - EXPECT_EQ("12345", utils::ParseECVersion("fw_version=12345")); - EXPECT_EQ("00VFA616", - utils::ParseECVersion("vendor=\"sam\" fw_version=\"00VFA616\"")); - - // For invalid entries, should return the empty string. - EXPECT_EQ("", utils::ParseECVersion("b=1231a fw_version a=fasd2")); -} - TEST(UtilsTest, WriteFileOpenFailure) { EXPECT_FALSE(utils::WriteFile("/this/doesn't/exist", "hello", 5)); } TEST(UtilsTest, WriteFileReadFile) { - test_utils::ScopedTempFile file; + ScopedTempFile file; EXPECT_TRUE(utils::WriteFile(file.path().c_str(), "hello", 5)); brillo::Blob readback; @@ -73,7 +60,7 @@ TEST(UtilsTest, ReadFileFailure) { } TEST(UtilsTest, ReadFileChunk) { - test_utils::ScopedTempFile file; + ScopedTempFile file; brillo::Blob data; const size_t kSize = 1024 * 1024; for (size_t i = 0; i < kSize; i++) { @@ -123,10 +110,6 @@ TEST(UtilsTest, SplitPartitionNameTest) { EXPECT_EQ("/dev/mmcblk0", disk); EXPECT_EQ(3, part_num); - EXPECT_TRUE(utils::SplitPartitionName("/dev/ubiblock3_2", &disk, &part_num)); - EXPECT_EQ("/dev/ubiblock", disk); - EXPECT_EQ(3, part_num); - EXPECT_TRUE(utils::SplitPartitionName("/dev/loop10", &disk, &part_num)); EXPECT_EQ("/dev/loop", disk); EXPECT_EQ(10, part_num); @@ -135,14 +118,6 @@ TEST(UtilsTest, SplitPartitionNameTest) { EXPECT_EQ("/dev/loop28", disk); EXPECT_EQ(11, part_num); - EXPECT_TRUE(utils::SplitPartitionName("/dev/loop10_0", &disk, &part_num)); - EXPECT_EQ("/dev/loop", disk); - EXPECT_EQ(10, part_num); - - EXPECT_TRUE(utils::SplitPartitionName("/dev/loop28p11_0", &disk, &part_num)); - EXPECT_EQ("/dev/loop28", disk); - EXPECT_EQ(11, part_num); - EXPECT_FALSE(utils::SplitPartitionName("/dev/mmcblk0p", &disk, &part_num)); EXPECT_FALSE(utils::SplitPartitionName("/dev/sda", &disk, &part_num)); EXPECT_FALSE(utils::SplitPartitionName("/dev/foo/bar", &disk, &part_num)); @@ -157,29 +132,6 @@ TEST(UtilsTest, MakePartitionNameTest) { EXPECT_EQ("/dev/mmcblk0p2", utils::MakePartitionName("/dev/mmcblk0", 2)); EXPECT_EQ("/dev/loop8", utils::MakePartitionName("/dev/loop", 8)); EXPECT_EQ("/dev/loop12p2", utils::MakePartitionName("/dev/loop12", 2)); - EXPECT_EQ("/dev/ubi5_0", utils::MakePartitionName("/dev/ubiblock", 5)); - EXPECT_EQ("/dev/mtd4", utils::MakePartitionName("/dev/ubiblock", 4)); - EXPECT_EQ("/dev/ubi3_0", utils::MakePartitionName("/dev/ubiblock", 3)); - EXPECT_EQ("/dev/mtd2", utils::MakePartitionName("/dev/ubiblock", 2)); - EXPECT_EQ("/dev/ubi1_0", utils::MakePartitionName("/dev/ubiblock", 1)); -} - -TEST(UtilsTest, MakePartitionNameForMountTest) { - EXPECT_EQ("/dev/sda4", utils::MakePartitionNameForMount("/dev/sda4")); - EXPECT_EQ("/dev/sda123", utils::MakePartitionNameForMount("/dev/sda123")); - EXPECT_EQ("/dev/mmcblk2", utils::MakePartitionNameForMount("/dev/mmcblk2")); - EXPECT_EQ("/dev/mmcblk0p2", - utils::MakePartitionNameForMount("/dev/mmcblk0p2")); - EXPECT_EQ("/dev/loop0", utils::MakePartitionNameForMount("/dev/loop0")); - EXPECT_EQ("/dev/loop8", utils::MakePartitionNameForMount("/dev/loop8")); - EXPECT_EQ("/dev/loop12p2", utils::MakePartitionNameForMount("/dev/loop12p2")); - EXPECT_EQ("/dev/ubiblock5_0", - utils::MakePartitionNameForMount("/dev/ubiblock5_0")); - EXPECT_EQ("/dev/mtd4", utils::MakePartitionNameForMount("/dev/ubi4_0")); - EXPECT_EQ("/dev/ubiblock3_0", - utils::MakePartitionNameForMount("/dev/ubiblock3")); - EXPECT_EQ("/dev/mtd2", utils::MakePartitionNameForMount("/dev/ubi2")); - EXPECT_EQ("/dev/ubi1_0", utils::MakePartitionNameForMount("/dev/ubiblock1")); } TEST(UtilsTest, FuzzIntTest) { @@ -197,7 +149,7 @@ TEST(UtilsTest, FuzzIntTest) { namespace { void GetFileFormatTester(const string& expected, const vector<uint8_t>& contents) { - test_utils::ScopedTempFile file; + ScopedTempFile file; ASSERT_TRUE(utils::WriteFile(file.path().c_str(), reinterpret_cast<const char*>(contents.data()), contents.size())); @@ -426,7 +378,7 @@ TEST(UtilsTest, RunAsRootUnmountFilesystemFailureTest) { } TEST(UtilsTest, RunAsRootUnmountFilesystemBusyFailureTest) { - test_utils::ScopedTempFile tmp_image("img.XXXXXX"); + ScopedTempFile tmp_image("img.XXXXXX"); EXPECT_TRUE(base::CopyFile( test_utils::GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"), @@ -466,7 +418,7 @@ TEST(UtilsTest, IsMountpointTest) { EXPECT_TRUE(mnt_dir.CreateUniqueTempDir()); EXPECT_FALSE(utils::IsMountpoint(mnt_dir.GetPath().value())); - test_utils::ScopedTempFile file; + ScopedTempFile file; EXPECT_FALSE(utils::IsMountpoint(file.path())); } @@ -508,7 +460,7 @@ TEST(UtilsTest, ParseDottedVersion) { } TEST(UtilsTest, GetFilePathTest) { - test_utils::ScopedTempFile file; + ScopedTempFile file; int fd = HANDLE_EINTR(open(file.path().c_str(), O_RDONLY)); EXPECT_GE(fd, 0); EXPECT_EQ(file.path(), utils::GetFilePath(fd)); @@ -516,4 +468,14 @@ TEST(UtilsTest, GetFilePathTest) { 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/connection_manager_android.cc b/connection_manager_android.cc deleted file mode 100644 index 9d0c57bb..00000000 --- a/connection_manager_android.cc +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (C) 2016 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/connection_manager_android.h" - -#include <memory> - -namespace chromeos_update_engine { - -namespace connection_manager { -std::unique_ptr<ConnectionManagerInterface> CreateConnectionManager( - SystemState* system_state) { - return std::unique_ptr<ConnectionManagerInterface>( - new ConnectionManagerAndroid()); -} -} // namespace connection_manager - -bool ConnectionManagerAndroid::GetConnectionProperties( - ConnectionType* out_type, ConnectionTethering* out_tethering) { - return false; -} -bool ConnectionManagerAndroid::IsUpdateAllowedOver( - ConnectionType type, ConnectionTethering tethering) const { - return true; -} -bool ConnectionManagerAndroid::IsAllowedConnectionTypesForUpdateSet() const { - return false; -} - -} // namespace chromeos_update_engine diff --git a/connection_manager_android.h b/connection_manager_android.h deleted file mode 100644 index 006f4ead..00000000 --- a/connection_manager_android.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (C) 2016 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_CONNECTION_MANAGER_ANDROID_H_ -#define UPDATE_ENGINE_CONNECTION_MANAGER_ANDROID_H_ - -#include <base/macros.h> - -#include "update_engine/connection_manager_interface.h" - -namespace chromeos_update_engine { - -// TODO(senj): Remove this class and use ShillProvider from the UpdateManager. -class ConnectionManagerAndroid : public ConnectionManagerInterface { - public: - ConnectionManagerAndroid() = default; - ~ConnectionManagerAndroid() override = default; - - // ConnectionManagerInterface overrides. - bool GetConnectionProperties(ConnectionType* out_type, - ConnectionTethering* out_tethering) override; - bool IsUpdateAllowedOver(ConnectionType type, - ConnectionTethering tethering) const override; - bool IsAllowedConnectionTypesForUpdateSet() const override; - - DISALLOW_COPY_AND_ASSIGN(ConnectionManagerAndroid); -}; - -} // namespace chromeos_update_engine - -#endif // UPDATE_ENGINE_CONNECTION_MANAGER_ANDROID_H_ diff --git a/boot_control_chromeos.cc b/cros/boot_control_chromeos.cc index 0f471696..17659ae7 100644 --- a/boot_control_chromeos.cc +++ b/cros/boot_control_chromeos.cc @@ -14,16 +14,19 @@ // limitations under the License. // -#include "update_engine/boot_control_chromeos.h" +#include "update_engine/cros/boot_control_chromeos.h" #include <memory> #include <string> #include <utility> +#include <vector> #include <base/bind.h> #include <base/files/file_path.h> #include <base/files/file_util.h> +#include <base/strings/string_split.h> #include <base/strings/string_util.h> +#include <chromeos/constants/imageloader.h> #include <rootdev/rootdev.h> extern "C" { @@ -36,6 +39,7 @@ extern "C" { #include "update_engine/common/utils.h" using std::string; +using std::vector; namespace { @@ -44,8 +48,7 @@ const char* kChromeOSPartitionNameRoot = "root"; const char* kAndroidPartitionNameKernel = "boot"; const char* kAndroidPartitionNameRoot = "system"; -const char kDlcInstallRootDirectoryEncrypted[] = "/home/chronos/dlc"; -const char kPartitionNamePrefixDlc[] = "dlc_"; +const char kPartitionNamePrefixDlc[] = "dlc"; const char kPartitionNameDlcA[] = "dlc_a"; const char kPartitionNameDlcB[] = "dlc_b"; const char kPartitionNameDlcImage[] = "dlc.img"; @@ -125,8 +128,9 @@ bool BootControlChromeOS::Init() { } if (current_slot_ >= num_slots_) { LOG(ERROR) << "Couldn't find the slot number corresponding to the " - << "partition " << boot_device << ", number of slots: " - << num_slots_ << ". This device is not updateable."; + << "partition " << boot_device + << ", number of slots: " << num_slots_ + << ". This device is not updateable."; num_slots_ = 1; current_slot_ = BootControlInterface::kInvalidSlot; return false; @@ -148,24 +152,47 @@ BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const { return current_slot_; } -bool BootControlChromeOS::GetPartitionDevice(const string& partition_name, - unsigned int slot, - string* device) const { +bool BootControlChromeOS::ParseDlcPartitionName( + const std::string partition_name, + std::string* dlc_id, + std::string* dlc_package) const { + CHECK_NE(dlc_id, nullptr); + CHECK_NE(dlc_package, nullptr); + + vector<string> tokens = base::SplitString( + partition_name, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + if (tokens.size() != 3 || tokens[0] != kPartitionNamePrefixDlc) { + LOG(ERROR) << "DLC partition name (" << partition_name + << ") is not well formatted."; + return false; + } + if (tokens[1].empty() || tokens[2].empty()) { + LOG(ERROR) << " partition name does not contain valid DLC ID (" << tokens[1] + << ") or package (" << tokens[2] << ")"; + return false; + } + + *dlc_id = tokens[1]; + *dlc_package = tokens[2]; + return true; +} + +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, base::CompareCase::SENSITIVE)) { - // Extract DLC module ID from partition_name (DLC module ID is the string - // after |kPartitionNamePrefixDlc| in partition_name). - const auto dlc_module_id = - partition_name.substr(strlen(kPartitionNamePrefixDlc)); - if (dlc_module_id.empty()) { - LOG(ERROR) << " partition name does not contain DLC module ID:" - << partition_name; + string dlc_id, dlc_package; + if (!ParseDlcPartitionName(partition_name, &dlc_id, &dlc_package)) return false; - } - *device = base::FilePath(kDlcInstallRootDirectoryEncrypted) - .Append(dlc_module_id) + + *device = base::FilePath(imageloader::kDlcImageRootpath) + .Append(dlc_id) + .Append(dlc_package) .Append(slot == 0 ? kPartitionNameDlcA : kPartitionNameDlcB) .Append(kPartitionNameDlcImage) .value(); @@ -180,9 +207,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) diff --git a/boot_control_chromeos.h b/cros/boot_control_chromeos.h index 02090522..0dff2c02 100644 --- a/boot_control_chromeos.h +++ b/cros/boot_control_chromeos.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_BOOT_CONTROL_CHROMEOS_H_ -#define UPDATE_ENGINE_BOOT_CONTROL_CHROMEOS_H_ +#ifndef UPDATE_ENGINE_CROS_BOOT_CONTROL_CHROMEOS_H_ +#define UPDATE_ENGINE_CROS_BOOT_CONTROL_CHROMEOS_H_ #include <memory> #include <string> @@ -47,6 +47,11 @@ 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; @@ -59,6 +64,7 @@ class BootControlChromeOS : public BootControlInterface { friend class BootControlChromeOSTest; FRIEND_TEST(BootControlChromeOSTest, SysfsBlockDeviceTest); FRIEND_TEST(BootControlChromeOSTest, GetPartitionNumberTest); + FRIEND_TEST(BootControlChromeOSTest, ParseDlcPartitionNameTest); // Returns the sysfs block device for a root block device. For example, // SysfsBlockDevice("/dev/sda") returns "/sys/block/sda". Returns an empty @@ -74,6 +80,13 @@ class BootControlChromeOS : public BootControlInterface { int GetPartitionNumber(const std::string partition_name, BootControlInterface::Slot slot) const; + // 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/fake-dlc/fake-package + bool ParseDlcPartitionName(const std::string partition_name, + std::string* dlc_id, + std::string* dlc_package) const; + // Cached values for GetNumSlots() and GetCurrentSlot(). BootControlInterface::Slot num_slots_{1}; BootControlInterface::Slot current_slot_{BootControlInterface::kInvalidSlot}; @@ -88,4 +101,4 @@ class BootControlChromeOS : public BootControlInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_BOOT_CONTROL_CHROMEOS_H_ +#endif // UPDATE_ENGINE_CROS_BOOT_CONTROL_CHROMEOS_H_ diff --git a/boot_control_chromeos_unittest.cc b/cros/boot_control_chromeos_unittest.cc index 6a600093..fc1dd1e5 100644 --- a/boot_control_chromeos_unittest.cc +++ b/cros/boot_control_chromeos_unittest.cc @@ -14,10 +14,12 @@ // limitations under the License. // -#include "update_engine/boot_control_chromeos.h" +#include "update_engine/cros/boot_control_chromeos.h" #include <gtest/gtest.h> +using std::string; + namespace chromeos_update_engine { class BootControlChromeOSTest : public ::testing::Test { @@ -67,4 +69,22 @@ TEST_F(BootControlChromeOSTest, GetPartitionNumberTest) { EXPECT_EQ(-1, bootctl_.GetPartitionNumber("A little panda", 0)); } +TEST_F(BootControlChromeOSTest, ParseDlcPartitionNameTest) { + string id, package; + + EXPECT_TRUE(bootctl_.ParseDlcPartitionName("dlc/id/package", &id, &package)); + EXPECT_EQ(id, "id"); + EXPECT_EQ(package, "package"); + + EXPECT_FALSE( + bootctl_.ParseDlcPartitionName("dlc-foo/id/package", &id, &package)); + EXPECT_FALSE( + bootctl_.ParseDlcPartitionName("dlc-foo/id/package/", &id, &package)); + EXPECT_FALSE(bootctl_.ParseDlcPartitionName("dlc/id", &id, &package)); + EXPECT_FALSE(bootctl_.ParseDlcPartitionName("dlc/id/", &id, &package)); + EXPECT_FALSE(bootctl_.ParseDlcPartitionName("dlc//package", &id, &package)); + EXPECT_FALSE(bootctl_.ParseDlcPartitionName("dlc", &id, &package)); + EXPECT_FALSE(bootctl_.ParseDlcPartitionName("foo", &id, &package)); +} + } // namespace chromeos_update_engine diff --git a/chrome_browser_proxy_resolver.cc b/cros/chrome_browser_proxy_resolver.cc index bfb58f7b..3ea8a9b1 100644 --- a/chrome_browser_proxy_resolver.cc +++ b/cros/chrome_browser_proxy_resolver.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/chrome_browser_proxy_resolver.h" +#include "update_engine/cros/chrome_browser_proxy_resolver.h" #include <utility> @@ -23,7 +23,7 @@ #include <base/strings/string_util.h> #include <brillo/http/http_proxy.h> -#include "update_engine/dbus_connection.h" +#include "update_engine/cros/dbus_connection.h" namespace chromeos_update_engine { diff --git a/chrome_browser_proxy_resolver.h b/cros/chrome_browser_proxy_resolver.h index 10a55fb2..76848ef3 100644 --- a/chrome_browser_proxy_resolver.h +++ b/cros/chrome_browser_proxy_resolver.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_ -#define UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_ +#ifndef UPDATE_ENGINE_CROS_CHROME_BROWSER_PROXY_RESOLVER_H_ +#define UPDATE_ENGINE_CROS_CHROME_BROWSER_PROXY_RESOLVER_H_ #include <deque> #include <map> @@ -63,4 +63,4 @@ class ChromeBrowserProxyResolver : public ProxyResolver { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_ +#endif // UPDATE_ENGINE_CROS_CHROME_BROWSER_PROXY_RESOLVER_H_ diff --git a/common_service.cc b/cros/common_service.cc index 0d5ee6dc..e5ee828c 100644 --- a/common_service.cc +++ b/cros/common_service.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/common_service.h" +#include "update_engine/cros/common_service.h" #include <string> @@ -26,16 +26,16 @@ #include <brillo/strings/string_utils.h> #include <policy/device_policy.h> -#include "update_engine/common/clock_interface.h" #include "update_engine/common/hardware_interface.h" #include "update_engine/common/prefs.h" +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" -#include "update_engine/connection_manager_interface.h" -#include "update_engine/omaha_request_params.h" -#include "update_engine/omaha_utils.h" -#include "update_engine/p2p_manager.h" -#include "update_engine/payload_state_interface.h" -#include "update_engine/update_attempter.h" +#include "update_engine/cros/connection_manager_interface.h" +#include "update_engine/cros/omaha_request_params.h" +#include "update_engine/cros/omaha_utils.h" +#include "update_engine/cros/p2p_manager.h" +#include "update_engine/cros/payload_state_interface.h" +#include "update_engine/cros/update_attempter.h" using base::StringPrintf; using brillo::ErrorPtr; @@ -50,11 +50,7 @@ namespace chromeos_update_engine { namespace { // Log and set the error on the passed ErrorPtr. void LogAndSetError(ErrorPtr* error, -#if BASE_VER < 576279 - const tracked_objects::Location& location, -#else const base::Location& location, -#endif const string& reason) { brillo::Error::AddTo(error, location, @@ -70,8 +66,7 @@ const char* const UpdateEngineService::kErrorDomain = "update_engine"; const char* const UpdateEngineService::kErrorFailed = "org.chromium.UpdateEngine.Error.Failed"; -UpdateEngineService::UpdateEngineService(SystemState* system_state) - : system_state_(system_state) {} +UpdateEngineService::UpdateEngineService() = default; // org::chromium::UpdateEngineInterfaceInterface methods implementation. @@ -83,7 +78,7 @@ bool UpdateEngineService::SetUpdateAttemptFlags(ErrorPtr* /* error */, << "RestrictDownload=" << ((flags & UpdateAttemptFlags::kFlagRestrictDownload) ? "yes" : "no"); - system_state_->update_attempter()->SetUpdateAttemptFlags(flags); + SystemState::Get()->update_attempter()->SetUpdateAttemptFlags(flags); return true; } @@ -102,16 +97,16 @@ bool UpdateEngineService::AttemptUpdate(ErrorPtr* /* error */, << "interactive=" << (interactive ? "yes " : "no ") << "RestrictDownload=" << (restrict_downloads ? "yes " : "no "); - *out_result = system_state_->update_attempter()->CheckForUpdate( + *out_result = SystemState::Get()->update_attempter()->CheckForUpdate( in_app_version, in_omaha_url, flags); return true; } bool UpdateEngineService::AttemptInstall(brillo::ErrorPtr* error, const string& omaha_url, - const vector<string>& dlc_module_ids) { - if (!system_state_->update_attempter()->CheckForInstall(dlc_module_ids, - omaha_url)) { + const vector<string>& dlc_ids) { + if (!SystemState::Get()->update_attempter()->CheckForInstall(dlc_ids, + omaha_url)) { // TODO(xiaochu): support more detailed error messages. LogAndSetError(error, FROM_HERE, "Could not schedule install operation."); return false; @@ -122,7 +117,7 @@ bool UpdateEngineService::AttemptInstall(brillo::ErrorPtr* error, bool UpdateEngineService::AttemptRollback(ErrorPtr* error, bool in_powerwash) { LOG(INFO) << "Attempting rollback to non-active partitions."; - if (!system_state_->update_attempter()->Rollback(in_powerwash)) { + if (!SystemState::Get()->update_attempter()->Rollback(in_powerwash)) { // TODO(dgarrett): Give a more specific error code/reason. LogAndSetError(error, FROM_HERE, "Rollback attempt failed."); return false; @@ -132,14 +127,14 @@ bool UpdateEngineService::AttemptRollback(ErrorPtr* error, bool in_powerwash) { bool UpdateEngineService::CanRollback(ErrorPtr* /* error */, bool* out_can_rollback) { - bool can_rollback = system_state_->update_attempter()->CanRollback(); + bool can_rollback = SystemState::Get()->update_attempter()->CanRollback(); LOG(INFO) << "Checking to see if we can rollback . Result: " << can_rollback; *out_can_rollback = can_rollback; return true; } bool UpdateEngineService::ResetStatus(ErrorPtr* error) { - if (!system_state_->update_attempter()->ResetStatus()) { + if (!SystemState::Get()->update_attempter()->ResetStatus()) { // TODO(dgarrett): Give a more specific error code/reason. LogAndSetError(error, FROM_HERE, "ResetStatus failed."); return false; @@ -147,9 +142,20 @@ bool UpdateEngineService::ResetStatus(ErrorPtr* error) { return true; } +bool UpdateEngineService::SetDlcActiveValue(brillo::ErrorPtr* error, + bool is_active, + const string& dlc_id) { + if (!SystemState::Get()->update_attempter()->SetDlcActiveValue(is_active, + dlc_id)) { + LogAndSetError(error, FROM_HERE, "SetDlcActiveValue failed."); + return false; + } + return true; +} + bool UpdateEngineService::GetStatus(ErrorPtr* error, UpdateEngineStatus* out_status) { - if (!system_state_->update_attempter()->GetStatus(out_status)) { + if (!SystemState::Get()->update_attempter()->GetStatus(out_status)) { LogAndSetError(error, FROM_HERE, "GetStatus failed."); return false; } @@ -157,7 +163,7 @@ bool UpdateEngineService::GetStatus(ErrorPtr* error, } bool UpdateEngineService::RebootIfNeeded(ErrorPtr* error) { - if (!system_state_->update_attempter()->RebootIfNeeded()) { + if (!SystemState::Get()->update_attempter()->RebootIfNeeded()) { // TODO(dgarrett): Give a more specific error code/reason. LogAndSetError(error, FROM_HERE, "Reboot not needed, or attempt failed."); return false; @@ -168,15 +174,16 @@ bool UpdateEngineService::RebootIfNeeded(ErrorPtr* error) { bool UpdateEngineService::SetChannel(ErrorPtr* error, const string& in_target_channel, bool in_is_powerwash_allowed) { - const policy::DevicePolicy* device_policy = system_state_->device_policy(); + const policy::DevicePolicy* device_policy = + SystemState::Get()->device_policy(); // The device_policy is loaded in a lazy way before an update check. Load it // now from the libbrillo cache if it wasn't already loaded. if (!device_policy) { - UpdateAttempter* update_attempter = system_state_->update_attempter(); + UpdateAttempter* update_attempter = SystemState::Get()->update_attempter(); if (update_attempter) { update_attempter->RefreshDevicePolicy(); - device_policy = system_state_->device_policy(); + device_policy = SystemState::Get()->device_policy(); } } @@ -192,7 +199,7 @@ bool UpdateEngineService::SetChannel(ErrorPtr* error, LOG(INFO) << "Setting destination channel to: " << in_target_channel; string error_message; - if (!system_state_->request_params()->SetTargetChannel( + if (!SystemState::Get()->request_params()->SetTargetChannel( in_target_channel, in_is_powerwash_allowed, &error_message)) { LogAndSetError(error, FROM_HERE, error_message); return false; @@ -203,20 +210,19 @@ bool UpdateEngineService::SetChannel(ErrorPtr* error, bool UpdateEngineService::GetChannel(ErrorPtr* /* error */, bool in_get_current_channel, string* out_channel) { - OmahaRequestParams* rp = system_state_->request_params(); + OmahaRequestParams* rp = SystemState::Get()->request_params(); *out_channel = (in_get_current_channel ? rp->current_channel() : rp->target_channel()); return true; } bool UpdateEngineService::SetCohortHint(ErrorPtr* error, - string in_cohort_hint) { - PrefsInterface* prefs = system_state_->prefs(); - + const string& in_cohort_hint) { // It is ok to override the cohort hint with an invalid value since it is // stored in stateful partition. The code reading it should sanitize it // anyway. - if (!prefs->SetString(kPrefsOmahaCohortHint, in_cohort_hint)) { + if (!SystemState::Get()->prefs()->SetString(kPrefsOmahaCohortHint, + in_cohort_hint)) { LogAndSetError( error, FROM_HERE, @@ -229,8 +235,7 @@ bool UpdateEngineService::SetCohortHint(ErrorPtr* error, bool UpdateEngineService::GetCohortHint(ErrorPtr* error, string* out_cohort_hint) { - PrefsInterface* prefs = system_state_->prefs(); - + const auto* prefs = SystemState::Get()->prefs(); *out_cohort_hint = ""; if (prefs->Exists(kPrefsOmahaCohortHint) && !prefs->GetString(kPrefsOmahaCohortHint, out_cohort_hint)) { @@ -242,9 +247,7 @@ bool UpdateEngineService::GetCohortHint(ErrorPtr* error, bool UpdateEngineService::SetP2PUpdatePermission(ErrorPtr* error, bool in_enabled) { - PrefsInterface* prefs = system_state_->prefs(); - - if (!prefs->SetBoolean(kPrefsP2PEnabled, in_enabled)) { + if (!SystemState::Get()->prefs()->SetBoolean(kPrefsP2PEnabled, in_enabled)) { LogAndSetError( error, FROM_HERE, @@ -257,8 +260,7 @@ bool UpdateEngineService::SetP2PUpdatePermission(ErrorPtr* error, bool UpdateEngineService::GetP2PUpdatePermission(ErrorPtr* error, bool* out_enabled) { - PrefsInterface* prefs = system_state_->prefs(); - + const auto* prefs = SystemState::Get()->prefs(); bool p2p_pref = false; // Default if no setting is present. if (prefs->Exists(kPrefsP2PEnabled) && !prefs->GetBoolean(kPrefsP2PEnabled, &p2p_pref)) { @@ -273,7 +275,7 @@ bool UpdateEngineService::GetP2PUpdatePermission(ErrorPtr* error, bool UpdateEngineService::SetUpdateOverCellularPermission(ErrorPtr* error, bool in_allowed) { ConnectionManagerInterface* connection_manager = - system_state_->connection_manager(); + SystemState::Get()->connection_manager(); // Check if this setting is allowed by the device policy. if (connection_manager->IsAllowedConnectionTypesForUpdateSet()) { @@ -286,11 +288,8 @@ bool UpdateEngineService::SetUpdateOverCellularPermission(ErrorPtr* error, // If the policy wasn't loaded yet, then it is still OK to change the local // setting because the policy will be checked again during the update check. - - PrefsInterface* prefs = system_state_->prefs(); - - if (!prefs || - !prefs->SetBoolean(kPrefsUpdateOverCellularPermission, in_allowed)) { + if (!SystemState::Get()->prefs()->SetBoolean( + kPrefsUpdateOverCellularPermission, in_allowed)) { LogAndSetError(error, FROM_HERE, string("Error setting the update over cellular to ") + @@ -305,7 +304,7 @@ bool UpdateEngineService::SetUpdateOverCellularTarget( const std::string& target_version, int64_t target_size) { ConnectionManagerInterface* connection_manager = - system_state_->connection_manager(); + SystemState::Get()->connection_manager(); // Check if this setting is allowed by the device policy. if (connection_manager->IsAllowedConnectionTypesForUpdateSet()) { @@ -319,10 +318,8 @@ bool UpdateEngineService::SetUpdateOverCellularTarget( // If the policy wasn't loaded yet, then it is still OK to change the local // setting because the policy will be checked again during the update check. - PrefsInterface* prefs = system_state_->prefs(); - - if (!prefs || - !prefs->SetString(kPrefsUpdateOverCellularTargetVersion, + auto* prefs = SystemState::Get()->prefs(); + if (!prefs->SetString(kPrefsUpdateOverCellularTargetVersion, target_version) || !prefs->SetInt64(kPrefsUpdateOverCellularTargetSize, target_size)) { LogAndSetError( @@ -335,16 +332,15 @@ bool UpdateEngineService::SetUpdateOverCellularTarget( bool UpdateEngineService::GetUpdateOverCellularPermission(ErrorPtr* error, bool* out_allowed) { ConnectionManagerInterface* connection_manager = - system_state_->connection_manager(); + SystemState::Get()->connection_manager(); if (connection_manager->IsAllowedConnectionTypesForUpdateSet()) { // We have device policy, so ignore the user preferences. *out_allowed = connection_manager->IsUpdateAllowedOver( ConnectionType::kCellular, ConnectionTethering::kUnknown); } else { - PrefsInterface* prefs = system_state_->prefs(); - - if (!prefs || !prefs->Exists(kPrefsUpdateOverCellularPermission)) { + const auto* prefs = SystemState::Get()->prefs(); + if (!prefs->Exists(kPrefsUpdateOverCellularPermission)) { // Update is not allowed as user preference is not set or not available. *out_allowed = false; return true; @@ -366,26 +362,26 @@ bool UpdateEngineService::GetUpdateOverCellularPermission(ErrorPtr* error, bool UpdateEngineService::GetDurationSinceUpdate(ErrorPtr* error, int64_t* out_usec_wallclock) { base::Time time; - if (!system_state_->update_attempter()->GetBootTimeAtUpdate(&time)) { + if (!SystemState::Get()->update_attempter()->GetBootTimeAtUpdate(&time)) { LogAndSetError(error, FROM_HERE, "No pending update."); return false; } - ClockInterface* clock = system_state_->clock(); + const auto* clock = SystemState::Get()->clock(); *out_usec_wallclock = (clock->GetBootTime() - time).InMicroseconds(); return true; } bool UpdateEngineService::GetPrevVersion(ErrorPtr* /* error */, string* out_prev_version) { - *out_prev_version = system_state_->update_attempter()->GetPrevVersion(); + *out_prev_version = SystemState::Get()->update_attempter()->GetPrevVersion(); return true; } bool UpdateEngineService::GetRollbackPartition( ErrorPtr* /* error */, string* out_rollback_partition_name) { BootControlInterface::Slot rollback_slot = - system_state_->update_attempter()->GetRollbackSlot(); + SystemState::Get()->update_attempter()->GetRollbackSlot(); if (rollback_slot == BootControlInterface::kInvalidSlot) { out_rollback_partition_name->clear(); @@ -393,7 +389,7 @@ bool UpdateEngineService::GetRollbackPartition( } string name; - if (!system_state_->boot_control()->GetPartitionDevice( + if (!SystemState::Get()->boot_control()->GetPartitionDevice( "KERNEL", rollback_slot, &name)) { LOG(ERROR) << "Invalid rollback device"; return false; @@ -407,25 +403,9 @@ bool UpdateEngineService::GetRollbackPartition( bool UpdateEngineService::GetLastAttemptError(ErrorPtr* /* error */, int32_t* out_last_attempt_error) { ErrorCode error_code = - system_state_->update_attempter()->GetAttemptErrorCode(); + SystemState::Get()->update_attempter()->GetAttemptErrorCode(); *out_last_attempt_error = static_cast<int>(error_code); return true; } -bool UpdateEngineService::GetEolStatus(ErrorPtr* error, - int32_t* out_eol_status) { - PrefsInterface* prefs = system_state_->prefs(); - - string str_eol_status; - if (prefs->Exists(kPrefsOmahaEolStatus) && - !prefs->GetString(kPrefsOmahaEolStatus, &str_eol_status)) { - LogAndSetError(error, FROM_HERE, "Error getting the end-of-life status."); - return false; - } - - // StringToEolStatus will return kSupported for invalid values. - *out_eol_status = static_cast<int32_t>(StringToEolStatus(str_eol_status)); - return true; -} - } // namespace chromeos_update_engine diff --git a/common_service.h b/cros/common_service.h index f93855d9..2c176c55 100644 --- a/common_service.h +++ b/cros/common_service.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_COMMON_SERVICE_H_ -#define UPDATE_ENGINE_COMMON_SERVICE_H_ +#ifndef UPDATE_ENGINE_CROS_SERVICE_H_ +#define UPDATE_ENGINE_CROS_SERVICE_H_ #include <inttypes.h> @@ -26,7 +26,6 @@ #include <brillo/errors/error.h> #include "update_engine/client_library/include/update_engine/update_status.h" -#include "update_engine/system_state.h" namespace chromeos_update_engine { @@ -38,7 +37,7 @@ class UpdateEngineService { // Generic service error. static const char* const kErrorFailed; - explicit UpdateEngineService(SystemState* system_state); + UpdateEngineService(); virtual ~UpdateEngineService() = default; // Set flags that influence how updates and checks are performed. These @@ -55,10 +54,10 @@ class UpdateEngineService { // Attempts a DLC module install operation. // |omaha_url|: the URL to query for update. - // |dlc_module_ids|: a list of DLC module IDs. + // |dlc_ids|: a list of DLC module IDs. bool AttemptInstall(brillo::ErrorPtr* error, const std::string& omaha_url, - const std::vector<std::string>& dlc_module_ids); + const std::vector<std::string>& dlc_ids); bool AttemptRollback(brillo::ErrorPtr* error, bool in_powerwash); @@ -70,6 +69,13 @@ class UpdateEngineService { // update. This is used for development only. bool ResetStatus(brillo::ErrorPtr* error); + // Sets the DLC as active or inactive. When set to active, the ping metadata + // for the DLC is updated accordingly. When set to inactive, the metadata + // for the DLC is deleted. + bool SetDlcActiveValue(brillo::ErrorPtr* error, + bool is_active, + const std::string& dlc_id); + // Returns the current status of the Update Engine. If an update is in // progress, the number of operations, size to download and overall progress // is reported. @@ -102,7 +108,8 @@ class UpdateEngineService { // Sets the current "cohort hint" value to |in_cohort_hint|. The cohort hint // is sent back to Omaha on every request and can be used as a hint of what // cohort should we be put on. - bool SetCohortHint(brillo::ErrorPtr* error, std::string in_cohort_hint); + bool SetCohortHint(brillo::ErrorPtr* error, + const std::string& in_cohort_hint); // Return the current cohort hint. This value can be set with SetCohortHint() // and can also be updated from Omaha on every update check request. @@ -152,15 +159,8 @@ class UpdateEngineService { // Returns the last UpdateAttempt error. bool GetLastAttemptError(brillo::ErrorPtr* error, int32_t* out_last_attempt_error); - - // Returns the current end-of-life status of the device. This value is updated - // on every update check and persisted on disk across reboots. - bool GetEolStatus(brillo::ErrorPtr* error, int32_t* out_eol_status); - - private: - SystemState* system_state_; }; } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_COMMON_SERVICE_H_ +#endif // UPDATE_ENGINE_CROS_SERVICE_H_ diff --git a/common_service_unittest.cc b/cros/common_service_unittest.cc index 65202a06..06446432 100644 --- a/common_service_unittest.cc +++ b/cros/common_service_unittest.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/common_service.h" +#include "update_engine/cros/common_service.h" #include <gtest/gtest.h> #include <string> @@ -24,9 +24,8 @@ #include <policy/libpolicy.h> #include <policy/mock_device_policy.h> -#include "update_engine/common/fake_prefs.h" -#include "update_engine/fake_system_state.h" -#include "update_engine/omaha_utils.h" +#include "update_engine/cros/fake_system_state.h" +#include "update_engine/cros/omaha_utils.h" using std::string; using std::vector; @@ -39,17 +38,14 @@ namespace chromeos_update_engine { class UpdateEngineServiceTest : public ::testing::Test { protected: - UpdateEngineServiceTest() - : mock_update_attempter_(fake_system_state_.mock_update_attempter()), - common_service_(&fake_system_state_) {} + UpdateEngineServiceTest() = default; - void SetUp() override { fake_system_state_.set_device_policy(nullptr); } + void SetUp() override { + FakeSystemState::CreateInstance(); + FakeSystemState::Get()->set_device_policy(nullptr); + mock_update_attempter_ = FakeSystemState::Get()->mock_update_attempter(); + } - // Fake/mock infrastructure. - FakeSystemState fake_system_state_; - policy::MockDevicePolicy mock_device_policy_; - - // Shortcut for fake_system_state_.mock_update_attempter(). MockUpdateAttempter* mock_update_attempter_; brillo::ErrorPtr error_; @@ -100,12 +96,26 @@ TEST_F(UpdateEngineServiceTest, AttemptInstallReturnsFalse) { EXPECT_FALSE(common_service_.AttemptInstall(&error_, "", {})); } +TEST_F(UpdateEngineServiceTest, SetDlcActiveValue) { + EXPECT_CALL(*mock_update_attempter_, SetDlcActiveValue(_, _)) + .WillOnce(Return(true)); + + EXPECT_TRUE(common_service_.SetDlcActiveValue(&error_, true, "dlc0")); +} + +TEST_F(UpdateEngineServiceTest, SetDlcActiveValueReturnsFalse) { + EXPECT_CALL(*mock_update_attempter_, SetDlcActiveValue(_, _)) + .WillOnce(Return(false)); + + EXPECT_FALSE(common_service_.SetDlcActiveValue(&error_, true, "dlc0")); +} + // SetChannel is allowed when there's no device policy (the device is not // enterprise enrolled). TEST_F(UpdateEngineServiceTest, SetChannelWithNoPolicy) { EXPECT_CALL(*mock_update_attempter_, RefreshDevicePolicy()); // If SetTargetChannel is called it means the policy check passed. - EXPECT_CALL(*fake_system_state_.mock_request_params(), + EXPECT_CALL(*FakeSystemState::Get()->mock_request_params(), SetTargetChannel("stable-channel", true, _)) .WillOnce(Return(true)); EXPECT_TRUE(common_service_.SetChannel(&error_, "stable-channel", true)); @@ -115,10 +125,10 @@ TEST_F(UpdateEngineServiceTest, SetChannelWithNoPolicy) { // When the policy is present, the delegated value should be checked. TEST_F(UpdateEngineServiceTest, SetChannelWithDelegatedPolicy) { policy::MockDevicePolicy mock_device_policy; - fake_system_state_.set_device_policy(&mock_device_policy); + FakeSystemState::Get()->set_device_policy(&mock_device_policy); EXPECT_CALL(mock_device_policy, GetReleaseChannelDelegated(_)) .WillOnce(DoAll(SetArgPointee<0>(true), Return(true))); - EXPECT_CALL(*fake_system_state_.mock_request_params(), + EXPECT_CALL(*FakeSystemState::Get()->mock_request_params(), SetTargetChannel("beta-channel", true, _)) .WillOnce(Return(true)); @@ -130,7 +140,7 @@ TEST_F(UpdateEngineServiceTest, SetChannelWithDelegatedPolicy) { // raised. TEST_F(UpdateEngineServiceTest, SetChannelWithInvalidChannel) { EXPECT_CALL(*mock_update_attempter_, RefreshDevicePolicy()); - EXPECT_CALL(*fake_system_state_.mock_request_params(), + EXPECT_CALL(*FakeSystemState::Get()->mock_request_params(), SetTargetChannel("foo-channel", true, _)) .WillOnce(Return(false)); @@ -141,8 +151,8 @@ TEST_F(UpdateEngineServiceTest, SetChannelWithInvalidChannel) { } TEST_F(UpdateEngineServiceTest, GetChannel) { - fake_system_state_.mock_request_params()->set_current_channel("current"); - fake_system_state_.mock_request_params()->set_target_channel("target"); + FakeSystemState::Get()->mock_request_params()->set_current_channel("current"); + FakeSystemState::Get()->mock_request_params()->set_target_channel("target"); string channel; EXPECT_TRUE(common_service_.GetChannel( &error_, true /* get_current_channel */, &channel)); @@ -169,19 +179,4 @@ TEST_F(UpdateEngineServiceTest, ResetStatusFails) { UpdateEngineService::kErrorFailed)); } -TEST_F(UpdateEngineServiceTest, GetEolStatusTest) { - FakePrefs fake_prefs; - fake_system_state_.set_prefs(&fake_prefs); - // The default value should be "supported". - int32_t eol_status = static_cast<int32_t>(EolStatus::kEol); - EXPECT_TRUE(common_service_.GetEolStatus(&error_, &eol_status)); - EXPECT_EQ(nullptr, error_); - EXPECT_EQ(EolStatus::kSupported, static_cast<EolStatus>(eol_status)); - - fake_prefs.SetString(kPrefsOmahaEolStatus, "security-only"); - EXPECT_TRUE(common_service_.GetEolStatus(&error_, &eol_status)); - EXPECT_EQ(nullptr, error_); - EXPECT_EQ(EolStatus::kSecurityOnly, static_cast<EolStatus>(eol_status)); -} - } // namespace chromeos_update_engine diff --git a/connection_manager.cc b/cros/connection_manager.cc index 7263a742..6a5c63b5 100644 --- a/connection_manager.cc +++ b/cros/connection_manager.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/connection_manager.h" +#include "update_engine/cros/connection_manager.h" #include <memory> #include <set> @@ -26,12 +26,12 @@ #include <shill/dbus-constants.h> #include <shill/dbus-proxies.h> +#include "update_engine/common/connection_utils.h" #include "update_engine/common/prefs.h" +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" -#include "update_engine/connection_utils.h" -#include "update_engine/shill_proxy.h" -#include "update_engine/system_state.h" -#include "update_engine/update_attempter.h" +#include "update_engine/cros/shill_proxy.h" +#include "update_engine/cros/update_attempter.h" using org::chromium::flimflam::ManagerProxyInterface; using org::chromium::flimflam::ServiceProxyInterface; @@ -41,83 +41,75 @@ using std::string; namespace chromeos_update_engine { namespace connection_manager { -std::unique_ptr<ConnectionManagerInterface> CreateConnectionManager( - SystemState* system_state) { +std::unique_ptr<ConnectionManagerInterface> CreateConnectionManager() { return std::unique_ptr<ConnectionManagerInterface>( - new ConnectionManager(new ShillProxy(), system_state)); + new ConnectionManager(new ShillProxy())); } } // namespace connection_manager -ConnectionManager::ConnectionManager(ShillProxyInterface* shill_proxy, - SystemState* system_state) - : shill_proxy_(shill_proxy), system_state_(system_state) {} +ConnectionManager::ConnectionManager(ShillProxyInterface* shill_proxy) + : shill_proxy_(shill_proxy) {} bool ConnectionManager::IsUpdateAllowedOver( ConnectionType type, ConnectionTethering tethering) const { - switch (type) { - case ConnectionType::kBluetooth: - return false; - - case ConnectionType::kCellular: { - set<string> allowed_types; - - const policy::DevicePolicy* device_policy = - system_state_->device_policy(); - - // The device_policy is loaded in a lazy way before an update check. Load - // it now from the libbrillo cache if it wasn't already loaded. - if (!device_policy) { - UpdateAttempter* update_attempter = system_state_->update_attempter(); - if (update_attempter) { - update_attempter->RefreshDevicePolicy(); - device_policy = system_state_->device_policy(); - } - } - - if (!device_policy) { - // Device policy fails to be loaded (possibly due to guest account). We - // do not check the local user setting here, which should be checked by - // |OmahaRequestAction| during checking for update. - LOG(INFO) << "Allowing updates over cellular as device policy " - "fails to be loaded."; - return true; - } - - if (device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) { - // The update setting is enforced by the device policy. - - if (!base::ContainsKey(allowed_types, shill::kTypeCellular)) { - LOG(INFO) << "Disabling updates over cellular connection as it's not " - "allowed in the device policy."; - return false; - } - - LOG(INFO) << "Allowing updates over cellular per device policy."; - return true; - } - - // If there's no update setting in the device policy, we do not check - // the local user setting here, which should be checked by - // |OmahaRequestAction| during checking for update. - LOG(INFO) << "Allowing updates over cellular as device policy does " - "not include update setting."; + if (type != ConnectionType::kCellular) { + if (tethering != ConnectionTethering::kConfirmed) { return true; } - default: - if (tethering == ConnectionTethering::kConfirmed) { - // Treat this connection as if it is a cellular connection. - LOG(INFO) << "Current connection is confirmed tethered, using Cellular " - "setting."; - return IsUpdateAllowedOver(ConnectionType::kCellular, - ConnectionTethering::kUnknown); - } - return true; + // Treat this connection as if it is a cellular connection. + LOG(INFO) + << "Current connection is confirmed tethered, using Cellular setting."; + } + + const policy::DevicePolicy* device_policy = + SystemState::Get()->device_policy(); + + // The device_policy is loaded in a lazy way before an update check. Load + // it now from the libbrillo cache if it wasn't already loaded. + if (!device_policy) { + UpdateAttempter* update_attempter = SystemState::Get()->update_attempter(); + if (update_attempter) { + update_attempter->RefreshDevicePolicy(); + device_policy = SystemState::Get()->device_policy(); + } + } + + if (!device_policy) { + // Device policy fails to be loaded (possibly due to guest account). We + // do not check the local user setting here, which should be checked by + // |OmahaRequestAction| during checking for update. + LOG(INFO) << "Allowing updates over cellular as device policy fails to be " + "loaded."; + return true; } + + set<string> allowed_types; + if (device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) { + // The update setting is enforced by the device policy. + + // TODO(crbug.com/1054279): Use base::Contains after uprev to r680000. + if (allowed_types.find(shill::kTypeCellular) == allowed_types.end()) { + LOG(INFO) << "Disabling updates over cellular connection as it's not " + "allowed in the device policy."; + return false; + } + + LOG(INFO) << "Allowing updates over cellular per device policy."; + return true; + } + + // If there's no update setting in the device policy, we do not check + // the local user setting here, which should be checked by + // |OmahaRequestAction| during checking for update. + LOG(INFO) << "Allowing updates over cellular as device policy does " + "not include update setting."; + return true; } bool ConnectionManager::IsAllowedConnectionTypesForUpdateSet() const { - const policy::DevicePolicy* device_policy = system_state_->device_policy(); + const policy::DevicePolicy* device_policy = + SystemState::Get()->device_policy(); if (!device_policy) { LOG(INFO) << "There's no device policy loaded yet."; return false; diff --git a/connection_manager.h b/cros/connection_manager.h index d8527a31..bb54ff7a 100644 --- a/connection_manager.h +++ b/cros/connection_manager.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_CONNECTION_MANAGER_H_ -#define UPDATE_ENGINE_CONNECTION_MANAGER_H_ +#ifndef UPDATE_ENGINE_CROS_CONNECTION_MANAGER_H_ +#define UPDATE_ENGINE_CROS_CONNECTION_MANAGER_H_ #include <memory> #include <string> @@ -23,8 +23,8 @@ #include <base/macros.h> #include <dbus/object_path.h> -#include "update_engine/connection_manager_interface.h" -#include "update_engine/shill_proxy_interface.h" +#include "update_engine/cros/connection_manager_interface.h" +#include "update_engine/cros/shill_proxy_interface.h" namespace chromeos_update_engine { @@ -35,8 +35,7 @@ class ConnectionManager : public ConnectionManagerInterface { public: // Constructs a new ConnectionManager object initialized with the // given system state. - ConnectionManager(ShillProxyInterface* shill_proxy, - SystemState* system_state); + explicit ConnectionManager(ShillProxyInterface* shill_proxy); ~ConnectionManager() override = default; // ConnectionManagerInterface overrides. @@ -58,12 +57,9 @@ class ConnectionManager : public ConnectionManagerInterface { // The mockable interface to access the shill DBus proxies. std::unique_ptr<ShillProxyInterface> shill_proxy_; - // The global context for update_engine. - SystemState* system_state_; - DISALLOW_COPY_AND_ASSIGN(ConnectionManager); }; } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_CONNECTION_MANAGER_H_ +#endif // UPDATE_ENGINE_CROS_CONNECTION_MANAGER_H_ diff --git a/connection_manager_interface.h b/cros/connection_manager_interface.h index 9f779891..dc6c9838 100644 --- a/connection_manager_interface.h +++ b/cros/connection_manager_interface.h @@ -14,19 +14,17 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_CONNECTION_MANAGER_INTERFACE_H_ -#define UPDATE_ENGINE_CONNECTION_MANAGER_INTERFACE_H_ +#ifndef UPDATE_ENGINE_CROS_CONNECTION_MANAGER_INTERFACE_H_ +#define UPDATE_ENGINE_CROS_CONNECTION_MANAGER_INTERFACE_H_ #include <memory> #include <base/macros.h> -#include "update_engine/connection_utils.h" +#include "update_engine/common/connection_utils.h" namespace chromeos_update_engine { -class SystemState; - // This class exposes a generic interface to the connection manager // (e.g FlimFlam, Shill, etc.) to consolidate all connection-related // logic in update_engine. @@ -59,10 +57,9 @@ class ConnectionManagerInterface { namespace connection_manager { // Factory function which creates a ConnectionManager. -std::unique_ptr<ConnectionManagerInterface> CreateConnectionManager( - SystemState* system_state); +std::unique_ptr<ConnectionManagerInterface> CreateConnectionManager(); } // namespace connection_manager } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_CONNECTION_MANAGER_INTERFACE_H_ +#endif // UPDATE_ENGINE_CROS_CONNECTION_MANAGER_INTERFACE_H_ diff --git a/connection_manager_unittest.cc b/cros/connection_manager_unittest.cc index 3cdaf4cd..46da8cc9 100644 --- a/connection_manager_unittest.cc +++ b/cros/connection_manager_unittest.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/connection_manager.h" +#include "update_engine/cros/connection_manager.h" #include <memory> #include <set> @@ -32,8 +32,8 @@ #include <shill/dbus-proxy-mocks.h> #include "update_engine/common/test_utils.h" -#include "update_engine/fake_shill_proxy.h" -#include "update_engine/fake_system_state.h" +#include "update_engine/cros/fake_shill_proxy.h" +#include "update_engine/cros/fake_system_state.h" using chromeos_update_engine::connection_utils::StringForConnectionType; using org::chromium::flimflam::ManagerProxyMock; @@ -52,7 +52,8 @@ class ConnectionManagerTest : public ::testing::Test { void SetUp() override { loop_.SetAsCurrent(); - fake_system_state_.set_connection_manager(&cmut_); + FakeSystemState::CreateInstance(); + FakeSystemState::Get()->set_connection_manager(&cmut_); } void TearDown() override { EXPECT_FALSE(loop_.PendingTasks()); } @@ -81,11 +82,10 @@ class ConnectionManagerTest : public ::testing::Test { ConnectionTethering expected_tethering); brillo::FakeMessageLoop loop_{nullptr}; - FakeSystemState fake_system_state_; FakeShillProxy* fake_shill_proxy_; // ConnectionManager under test. - ConnectionManager cmut_{fake_shill_proxy_, &fake_system_state_}; + ConnectionManager cmut_{fake_shill_proxy_}; }; void ConnectionManagerTest::SetManagerReply(const char* default_service, @@ -184,9 +184,6 @@ void ConnectionManagerTest::TestWithServiceDisconnected( TEST_F(ConnectionManagerTest, SimpleTest) { TestWithServiceType(shill::kTypeEthernet, nullptr, ConnectionType::kEthernet); TestWithServiceType(shill::kTypeWifi, nullptr, ConnectionType::kWifi); - TestWithServiceType(shill::kTypeWimax, nullptr, ConnectionType::kWimax); - TestWithServiceType( - shill::kTypeBluetooth, nullptr, ConnectionType::kBluetooth); TestWithServiceType(shill::kTypeCellular, nullptr, ConnectionType::kCellular); } @@ -195,8 +192,6 @@ TEST_F(ConnectionManagerTest, PhysicalTechnologyTest) { TestWithServiceType( shill::kTypeVPN, shill::kTypeVPN, ConnectionType::kUnknown); TestWithServiceType(shill::kTypeVPN, shill::kTypeWifi, ConnectionType::kWifi); - TestWithServiceType( - shill::kTypeVPN, shill::kTypeWimax, ConnectionType::kWimax); } TEST_F(ConnectionManagerTest, TetheringTest) { @@ -229,20 +224,10 @@ TEST_F(ConnectionManagerTest, AllowUpdatesOverWifiTest) { ConnectionTethering::kUnknown)); } -TEST_F(ConnectionManagerTest, AllowUpdatesOverWimaxTest) { - EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWimax, - ConnectionTethering::kUnknown)); -} - -TEST_F(ConnectionManagerTest, BlockUpdatesOverBluetoothTest) { - EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kBluetooth, - ConnectionTethering::kUnknown)); -} - TEST_F(ConnectionManagerTest, AllowUpdatesOnlyOver3GPerPolicyTest) { policy::MockDevicePolicy allow_3g_policy; - fake_system_state_.set_device_policy(&allow_3g_policy); + FakeSystemState::Get()->set_device_policy(&allow_3g_policy); // This test tests cellular (3G) being the only connection type being allowed. set<string> allowed_set; @@ -259,14 +244,13 @@ TEST_F(ConnectionManagerTest, AllowUpdatesOnlyOver3GPerPolicyTest) { TEST_F(ConnectionManagerTest, AllowUpdatesOver3GAndOtherTypesPerPolicyTest) { policy::MockDevicePolicy allow_3g_policy; - fake_system_state_.set_device_policy(&allow_3g_policy); + FakeSystemState::Get()->set_device_policy(&allow_3g_policy); // This test tests multiple connection types being allowed, with // 3G one among them. Only Cellular is currently enforced by the policy - // setting, the others are ignored (see Bluetooth for example). + // setting. set<string> allowed_set; allowed_set.insert(StringForConnectionType(ConnectionType::kCellular)); - allowed_set.insert(StringForConnectionType(ConnectionType::kBluetooth)); EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_)) .Times(3) @@ -280,10 +264,6 @@ TEST_F(ConnectionManagerTest, AllowUpdatesOver3GAndOtherTypesPerPolicyTest) { ConnectionTethering::kUnknown)); EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi, ConnectionTethering::kUnknown)); - EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWimax, - ConnectionTethering::kUnknown)); - EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kBluetooth, - ConnectionTethering::kUnknown)); // Tethered networks are treated in the same way as Cellular networks and // thus allowed. @@ -296,7 +276,7 @@ TEST_F(ConnectionManagerTest, AllowUpdatesOver3GAndOtherTypesPerPolicyTest) { TEST_F(ConnectionManagerTest, AllowUpdatesOverCellularByDefaultTest) { policy::MockDevicePolicy device_policy; // Set an empty device policy. - fake_system_state_.set_device_policy(&device_policy); + FakeSystemState::Get()->set_device_policy(&device_policy); EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular, ConnectionTethering::kUnknown)); @@ -305,7 +285,7 @@ TEST_F(ConnectionManagerTest, AllowUpdatesOverCellularByDefaultTest) { TEST_F(ConnectionManagerTest, AllowUpdatesOverTetheredNetworkByDefaultTest) { policy::MockDevicePolicy device_policy; // Set an empty device policy. - fake_system_state_.set_device_policy(&device_policy); + FakeSystemState::Get()->set_device_policy(&device_policy); EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi, ConnectionTethering::kConfirmed)); @@ -318,14 +298,13 @@ TEST_F(ConnectionManagerTest, AllowUpdatesOverTetheredNetworkByDefaultTest) { TEST_F(ConnectionManagerTest, BlockUpdatesOver3GPerPolicyTest) { policy::MockDevicePolicy block_3g_policy; - fake_system_state_.set_device_policy(&block_3g_policy); + FakeSystemState::Get()->set_device_policy(&block_3g_policy); // Test that updates for 3G are blocked while updates are allowed // over several other types. set<string> allowed_set; allowed_set.insert(StringForConnectionType(ConnectionType::kEthernet)); allowed_set.insert(StringForConnectionType(ConnectionType::kWifi)); - allowed_set.insert(StringForConnectionType(ConnectionType::kWimax)); EXPECT_CALL(block_3g_policy, GetAllowedConnectionTypesForUpdate(_)) .Times(1) @@ -338,7 +317,7 @@ TEST_F(ConnectionManagerTest, BlockUpdatesOver3GPerPolicyTest) { TEST_F(ConnectionManagerTest, AllowUpdatesOver3GIfPolicyIsNotSet) { policy::MockDevicePolicy device_policy; - fake_system_state_.set_device_policy(&device_policy); + FakeSystemState::Get()->set_device_policy(&device_policy); // Return false for GetAllowedConnectionTypesForUpdate and see // that updates are allowed as device policy is not set. Further @@ -352,7 +331,7 @@ TEST_F(ConnectionManagerTest, AllowUpdatesOver3GIfPolicyIsNotSet) { } TEST_F(ConnectionManagerTest, AllowUpdatesOverCellularIfPolicyFailsToBeLoaded) { - fake_system_state_.set_device_policy(nullptr); + FakeSystemState::Get()->set_device_policy(nullptr); EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular, ConnectionTethering::kUnknown)); @@ -363,10 +342,6 @@ TEST_F(ConnectionManagerTest, StringForConnectionTypeTest) { StringForConnectionType(ConnectionType::kEthernet)); EXPECT_STREQ(shill::kTypeWifi, StringForConnectionType(ConnectionType::kWifi)); - EXPECT_STREQ(shill::kTypeWimax, - StringForConnectionType(ConnectionType::kWimax)); - EXPECT_STREQ(shill::kTypeBluetooth, - StringForConnectionType(ConnectionType::kBluetooth)); EXPECT_STREQ(shill::kTypeCellular, StringForConnectionType(ConnectionType::kCellular)); EXPECT_STREQ("Unknown", StringForConnectionType(ConnectionType::kUnknown)); diff --git a/cros/daemon_chromeos.cc b/cros/daemon_chromeos.cc new file mode 100644 index 00000000..366fb9a9 --- /dev/null +++ b/cros/daemon_chromeos.cc @@ -0,0 +1,79 @@ +// +// Copyright (C) 2015 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/cros/daemon_chromeos.h" + +#include <sysexits.h> + +#include <base/bind.h> +#include <base/location.h> + +#include "update_engine/cros/real_system_state.h" + +using brillo::Daemon; +using std::unique_ptr; + +namespace chromeos_update_engine { + +unique_ptr<DaemonBase> DaemonBase::CreateInstance() { + return std::make_unique<DaemonChromeOS>(); +} + +int DaemonChromeOS::OnInit() { + // Register the |subprocess_| singleton with this Daemon as the signal + // handler. + subprocess_.Init(this); + + int exit_code = Daemon::OnInit(); + if (exit_code != EX_OK) + return exit_code; + + // Initialize update engine global state. + // TODO(deymo): Move the initialization to a factory method avoiding the + // explicit re-usage of the |bus| instance, shared between D-Bus service and + // D-Bus client calls. + RealSystemState::SetInstance(&system_state_); + + // Create the DBus service. + dbus_adaptor_.reset(new UpdateEngineAdaptor()); + SystemState::Get()->update_attempter()->AddObserver(dbus_adaptor_.get()); + + dbus_adaptor_->RegisterAsync( + base::Bind(&DaemonChromeOS::OnDBusRegistered, base::Unretained(this))); + LOG(INFO) << "Waiting for DBus object to be registered."; + return EX_OK; +} + +void DaemonChromeOS::OnDBusRegistered(bool succeeded) { + if (!succeeded) { + LOG(ERROR) << "Registering the UpdateEngineAdaptor"; + QuitWithExitCode(1); + return; + } + + // Take ownership of the service now that everything is initialized. We need + // to this now and not before to avoid exposing a well known DBus service + // path that doesn't have the service it is supposed to implement. + if (!dbus_adaptor_->RequestOwnership()) { + LOG(ERROR) << "Unable to take ownership of the DBus service, is there " + << "other update_engine daemon running?"; + QuitWithExitCode(1); + return; + } + SystemState::Get()->update_attempter()->StartUpdater(); +} + +} // namespace chromeos_update_engine diff --git a/daemon.h b/cros/daemon_chromeos.h index c10bb284..b23c2a6f 100644 --- a/daemon.h +++ b/cros/daemon_chromeos.h @@ -14,74 +14,50 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_DAEMON_H_ -#define UPDATE_ENGINE_DAEMON_H_ +#ifndef UPDATE_ENGINE_CROS_DAEMON_CHROMEOS_H_ +#define UPDATE_ENGINE_CROS_DAEMON_CHROMEOS_H_ #include <memory> -#include <string> -#if USE_BINDER -#include <brillo/binder_watcher.h> -#endif // USE_BINDER -#include <brillo/daemons/daemon.h> - -#if USE_BINDER -#if USE_OMAHA -#include "update_engine/binder_service_brillo.h" -#else // !USE_OMAHA -#include "update_engine/binder_service_android.h" -#endif // USE_OMAHA -#endif // USE_BINDER +#include "update_engine/common/daemon_base.h" +#include "update_engine/common/daemon_state_interface.h" #include "update_engine/common/subprocess.h" -#include "update_engine/daemon_state_interface.h" -#if USE_DBUS -#include "update_engine/dbus_service.h" -#endif // USE_DBUS +#include "update_engine/cros/dbus_service.h" +#include "update_engine/cros/real_system_state.h" namespace chromeos_update_engine { -class UpdateEngineDaemon : public brillo::Daemon { +class DaemonChromeOS : public DaemonBase { public: - UpdateEngineDaemon() = default; + DaemonChromeOS() = default; protected: int OnInit() override; private: -#if USE_DBUS // Run from the main loop when the |dbus_adaptor_| object is registered. At // this point we can request ownership of the DBus service name and continue // initialization. void OnDBusRegistered(bool succeeded); + // |SystemState| is a global context, but we can't have a static singleton of + // its object because the style guide does not allow that (it has non-trivial + // dtor). We need an instance of |SystemState| in this class instead and have + // a global pointer to it. This is better to be defined as the first variable + // of this class so it is initialized first and destructed last. + RealSystemState system_state_; + // Main D-Bus service adaptor. std::unique_ptr<UpdateEngineAdaptor> dbus_adaptor_; -#endif // USE_DBUS // The Subprocess singleton class requires a brillo::MessageLoop in the // current thread, so we need to initialize it from this class instead of // the main() function. Subprocess subprocess_; -#if USE_BINDER - brillo::BinderWatcher binder_watcher_; -#endif // USE_BINDER - -#if USE_BINDER -#if USE_OMAHA - android::sp<BinderUpdateEngineBrilloService> binder_service_; -#else // !USE_OMAHA - android::sp<BinderUpdateEngineAndroidService> binder_service_; -#endif // USE_OMAHA -#endif // USE_BINDER - - // The daemon state with all the required daemon classes for the configured - // platform. - std::unique_ptr<DaemonStateInterface> daemon_state_; - - DISALLOW_COPY_AND_ASSIGN(UpdateEngineDaemon); + DISALLOW_COPY_AND_ASSIGN(DaemonChromeOS); }; } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_DAEMON_H_ +#endif // UPDATE_ENGINE_CROS_DAEMON_CHROMEOS_H_ diff --git a/dbus_connection.cc b/cros/dbus_connection.cc index cf17ec99..6808bae6 100644 --- a/dbus_connection.cc +++ b/cros/dbus_connection.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/dbus_connection.h" +#include "update_engine/cros/dbus_connection.h" #include <base/time/time.h> diff --git a/dbus_connection.h b/cros/dbus_connection.h index c3205ba5..8f0d6f1f 100644 --- a/dbus_connection.h +++ b/cros/dbus_connection.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_DBUS_CONNECTION_H_ -#define UPDATE_ENGINE_DBUS_CONNECTION_H_ +#ifndef UPDATE_ENGINE_CROS_DBUS_CONNECTION_H_ +#define UPDATE_ENGINE_CROS_DBUS_CONNECTION_H_ #include <base/memory/ref_counted.h> #include <brillo/dbus/dbus_connection.h> @@ -41,4 +41,4 @@ class DBusConnection { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_DBUS_CONNECTION_H_ +#endif // UPDATE_ENGINE_CROS_DBUS_CONNECTION_H_ diff --git a/dbus_service.cc b/cros/dbus_service.cc index 72960532..1eb7b3c0 100644 --- a/dbus_service.cc +++ b/cros/dbus_service.cc @@ -14,15 +14,15 @@ // limitations under the License. // -#include "update_engine/dbus_service.h" +#include "update_engine/cros/dbus_service.h" #include <string> #include <vector> #include <update_engine/dbus-constants.h> -#include <update_engine/proto_bindings/update_engine.pb.h> -#include "update_engine/dbus_connection.h" +#include "update_engine/cros/dbus_connection.h" +#include "update_engine/proto_bindings/update_engine.pb.h" #include "update_engine/update_status_utils.h" namespace chromeos_update_engine { @@ -31,10 +31,29 @@ using brillo::ErrorPtr; using chromeos_update_engine::UpdateEngineService; using std::string; using std::vector; +using update_engine::Operation; +using update_engine::StatusResult; using update_engine::UpdateEngineStatus; -DBusUpdateEngineService::DBusUpdateEngineService(SystemState* system_state) - : common_(new UpdateEngineService{system_state}) {} +namespace { +// Converts the internal |UpdateEngineStatus| to the protobuf |StatusResult|. +void ConvertToStatusResult(const UpdateEngineStatus& ue_status, + StatusResult* out_status) { + out_status->set_last_checked_time(ue_status.last_checked_time); + out_status->set_progress(ue_status.progress); + out_status->set_current_operation(static_cast<Operation>(ue_status.status)); + out_status->set_new_version(ue_status.new_version); + out_status->set_new_size(ue_status.new_size_bytes); + out_status->set_is_enterprise_rollback(ue_status.is_enterprise_rollback); + out_status->set_is_install(ue_status.is_install); + out_status->set_eol_date(ue_status.eol_date); + out_status->set_will_powerwash_after_reboot( + ue_status.will_powerwash_after_reboot); +} +} // namespace + +DBusUpdateEngineService::DBusUpdateEngineService() + : common_(new UpdateEngineService()) {} // org::chromium::UpdateEngineInterfaceInterface methods implementation. @@ -63,26 +82,9 @@ bool DBusUpdateEngineService::AttemptUpdateWithFlags( } bool DBusUpdateEngineService::AttemptInstall(ErrorPtr* error, - const string& dlc_request) { - // Parse the raw parameters into protobuf. - DlcParameters dlc_parameters; - if (!dlc_parameters.ParseFromString(dlc_request)) { - *error = brillo::Error::Create( - FROM_HERE, "update_engine", "INTERNAL", "parameters are invalid."); - return false; - } - // Extract fields from the protobuf. - vector<string> dlc_module_ids; - for (const auto& dlc_info : dlc_parameters.dlc_infos()) { - if (dlc_info.dlc_id().empty()) { - *error = brillo::Error::Create( - FROM_HERE, "update_engine", "INTERNAL", "parameters are invalid."); - return false; - } - dlc_module_ids.push_back(dlc_info.dlc_id()); - } - return common_->AttemptInstall( - error, dlc_parameters.omaha_url(), dlc_module_ids); + const string& in_omaha_url, + const vector<string>& dlc_ids) { + return common_->AttemptInstall(error, in_omaha_url, dlc_ids); } bool DBusUpdateEngineService::AttemptRollback(ErrorPtr* error, @@ -99,21 +101,20 @@ bool DBusUpdateEngineService::ResetStatus(ErrorPtr* error) { return common_->ResetStatus(error); } -bool DBusUpdateEngineService::GetStatus(ErrorPtr* error, - int64_t* out_last_checked_time, - double* out_progress, - string* out_current_operation, - string* out_new_version, - int64_t* out_new_size) { +bool DBusUpdateEngineService::SetDlcActiveValue(brillo::ErrorPtr* error, + bool is_active, + const string& dlc_id) { + return common_->SetDlcActiveValue(error, is_active, dlc_id); +} + +bool DBusUpdateEngineService::GetStatusAdvanced(ErrorPtr* error, + StatusResult* out_status) { UpdateEngineStatus status; if (!common_->GetStatus(error, &status)) { return false; } - *out_last_checked_time = status.last_checked_time; - *out_progress = status.progress; - *out_current_operation = UpdateStatusToString(status.status); - *out_new_version = status.new_version; - *out_new_size = status.new_size_bytes; + + ConvertToStatusResult(status, out_status); return true; } @@ -191,15 +192,10 @@ bool DBusUpdateEngineService::GetLastAttemptError( return common_->GetLastAttemptError(error, out_last_attempt_error); } -bool DBusUpdateEngineService::GetEolStatus(ErrorPtr* error, - int32_t* out_eol_status) { - return common_->GetEolStatus(error, out_eol_status); -} - -UpdateEngineAdaptor::UpdateEngineAdaptor(SystemState* system_state) +UpdateEngineAdaptor::UpdateEngineAdaptor() : org::chromium::UpdateEngineInterfaceAdaptor(&dbus_service_), bus_(DBusConnection::Get()->GetDBus()), - dbus_service_(system_state), + dbus_service_(), dbus_object_(nullptr, bus_, dbus::ObjectPath(update_engine::kUpdateEngineServicePath)) {} @@ -217,11 +213,11 @@ bool UpdateEngineAdaptor::RequestOwnership() { void UpdateEngineAdaptor::SendStatusUpdate( const UpdateEngineStatus& update_engine_status) { - SendStatusUpdateSignal(update_engine_status.last_checked_time, - update_engine_status.progress, - UpdateStatusToString(update_engine_status.status), - update_engine_status.new_version, - update_engine_status.new_size_bytes); + StatusResult status; + ConvertToStatusResult(update_engine_status, &status); + + // Send |StatusUpdateAdvanced| signal. + SendStatusUpdateAdvancedSignal(status); } } // namespace chromeos_update_engine diff --git a/dbus_service.h b/cros/dbus_service.h index 134461bd..3ad6589e 100644 --- a/dbus_service.h +++ b/cros/dbus_service.h @@ -14,20 +14,22 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_DBUS_SERVICE_H_ -#define UPDATE_ENGINE_DBUS_SERVICE_H_ +#ifndef UPDATE_ENGINE_CROS_DBUS_SERVICE_H_ +#define UPDATE_ENGINE_CROS_DBUS_SERVICE_H_ #include <inttypes.h> #include <memory> #include <string> +#include <vector> #include <base/memory/ref_counted.h> #include <brillo/errors/error.h> +#include <update_engine/proto_bindings/update_engine.pb.h> -#include "update_engine/common_service.h" -#include "update_engine/service_observer_interface.h" -#include "update_engine/update_attempter.h" +#include "update_engine/common/service_observer_interface.h" +#include "update_engine/cros/common_service.h" +#include "update_engine/cros/update_attempter.h" #include "dbus_bindings/org.chromium.UpdateEngineInterface.h" @@ -36,7 +38,7 @@ namespace chromeos_update_engine { class DBusUpdateEngineService : public org::chromium::UpdateEngineInterfaceInterface { public: - explicit DBusUpdateEngineService(SystemState* system_state); + DBusUpdateEngineService(); virtual ~DBusUpdateEngineService() = default; // Implementation of org::chromium::UpdateEngineInterfaceInterface. @@ -50,7 +52,8 @@ class DBusUpdateEngineService int32_t in_flags_as_int) override; bool AttemptInstall(brillo::ErrorPtr* error, - const std::string& dlc_request) override; + const std::string& in_omaha_url, + const std::vector<std::string>& dlc_ids) override; bool AttemptRollback(brillo::ErrorPtr* error, bool in_powerwash) override; @@ -62,15 +65,17 @@ class DBusUpdateEngineService // update. This is used for development only. bool ResetStatus(brillo::ErrorPtr* error) override; - // Returns the current status of the Update Engine. If an update is in - // progress, the number of operations, size to download and overall progress - // is reported. - bool GetStatus(brillo::ErrorPtr* error, - int64_t* out_last_checked_time, - double* out_progress, - std::string* out_current_operation, - std::string* out_new_version, - int64_t* out_new_size) override; + // Sets the DLC as active or inactive. When set to active, the ping metadata + // for the DLC is updated accordingly. When set to inactive, the metadata + // for the DLC is deleted. + bool SetDlcActiveValue(brillo::ErrorPtr* error, + bool is_active, + const std::string& dlc_id) override; + + // Similar to Above, but returns a protobuffer instead. In the future it will + // have more features and is easily extendable. + bool GetStatusAdvanced(brillo::ErrorPtr* error, + update_engine::StatusResult* out_status) override; // Reboots the device if an update is applied and a reboot is required. bool RebootIfNeeded(brillo::ErrorPtr* error) override; @@ -150,9 +155,6 @@ class DBusUpdateEngineService bool GetLastAttemptError(brillo::ErrorPtr* error, int32_t* out_last_attempt_error) override; - // Returns the current end-of-life status of the device in |out_eol_status|. - bool GetEolStatus(brillo::ErrorPtr* error, int32_t* out_eol_status) override; - private: std::unique_ptr<UpdateEngineService> common_; }; @@ -163,7 +165,7 @@ class DBusUpdateEngineService class UpdateEngineAdaptor : public org::chromium::UpdateEngineInterfaceAdaptor, public ServiceObserverInterface { public: - explicit UpdateEngineAdaptor(SystemState* system_state); + UpdateEngineAdaptor(); ~UpdateEngineAdaptor() = default; // Register the DBus object with the update engine service asynchronously. @@ -189,4 +191,4 @@ class UpdateEngineAdaptor : public org::chromium::UpdateEngineInterfaceAdaptor, } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_DBUS_SERVICE_H_ +#endif // UPDATE_ENGINE_CROS_DBUS_SERVICE_H_ diff --git a/dbus_test_utils.h b/cros/dbus_test_utils.h index b3748cea..1116c528 100644 --- a/dbus_test_utils.h +++ b/cros/dbus_test_utils.h @@ -14,11 +14,13 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_DBUS_TEST_UTILS_H_ -#define UPDATE_ENGINE_DBUS_TEST_UTILS_H_ +#ifndef UPDATE_ENGINE_CROS_DBUS_TEST_UTILS_H_ +#define UPDATE_ENGINE_CROS_DBUS_TEST_UTILS_H_ +#include <memory> #include <set> #include <string> +#include <utility> #include <base/bind.h> #include <brillo/message_loops/message_loop.h> @@ -27,13 +29,13 @@ namespace chromeos_update_engine { namespace dbus_test_utils { -#define MOCK_SIGNAL_HANDLER_EXPECT_SIGNAL_HANDLER( \ - mock_signal_handler, mock_proxy, signal) \ - do { \ - EXPECT_CALL((mock_proxy), \ - Register##signal##SignalHandler(::testing::_, ::testing::_)) \ - .WillOnce(::chromeos_update_engine::dbus_test_utils::GrabCallbacks( \ - &(mock_signal_handler))); \ +#define MOCK_SIGNAL_HANDLER_EXPECT_SIGNAL_HANDLER( \ + mock_signal_handler, mock_proxy, signal) \ + do { \ + EXPECT_CALL((mock_proxy), \ + DoRegister##signal##SignalHandler(::testing::_, ::testing::_)) \ + .WillOnce(::chromeos_update_engine::dbus_test_utils::GrabCallbacks( \ + &(mock_signal_handler))); \ } while (false) template <typename T> @@ -52,10 +54,10 @@ class MockSignalHandler { void GrabCallbacks( const base::Callback<T>& signal_callback, - dbus::ObjectProxy::OnConnectedCallback on_connected_callback) { + dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) { signal_callback_.reset(new base::Callback<T>(signal_callback)); - on_connected_callback_.reset( - new dbus::ObjectProxy::OnConnectedCallback(on_connected_callback)); + on_connected_callback_.reset(new dbus::ObjectProxy::OnConnectedCallback( + std::move(*on_connected_callback))); // Notify from the main loop that the callback was connected. callback_connected_task_ = brillo::MessageLoop::current()->PostTask( FROM_HERE, @@ -66,7 +68,7 @@ class MockSignalHandler { private: void OnCallbackConnected() { callback_connected_task_ = brillo::MessageLoop::kTaskIdNull; - on_connected_callback_->Run("", "", true); + std::move(*on_connected_callback_).Run("", "", true); } brillo::MessageLoop::TaskId callback_connected_task_{ @@ -86,4 +88,4 @@ ACTION_P(GrabCallbacks, mock_signal_handler) { } // namespace dbus_test_utils } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_DBUS_TEST_UTILS_H_ +#endif // UPDATE_ENGINE_CROS_DBUS_TEST_UTILS_H_ diff --git a/cros/dlcservice_chromeos.cc b/cros/dlcservice_chromeos.cc new file mode 100644 index 00000000..e510c1d1 --- /dev/null +++ b/cros/dlcservice_chromeos.cc @@ -0,0 +1,77 @@ +// +// 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/cros/dlcservice_chromeos.h" + +#include <brillo/errors/error.h> +#include <dlcservice/proto_bindings/dlcservice.pb.h> +// NOLINTNEXTLINE(build/include_alpha) "dbus-proxies.h" needs "dlcservice.pb.h" +#include <dlcservice/dbus-proxies.h> + +#include "update_engine/cros/dbus_connection.h" + +using std::string; +using std::vector; + +namespace chromeos_update_engine { + +namespace { +org::chromium::DlcServiceInterfaceProxy GetDlcServiceProxy() { + return {DBusConnection::Get()->GetDBus()}; +} +} // namespace + +std::unique_ptr<DlcServiceInterface> CreateDlcService() { + return std::make_unique<DlcServiceChromeOS>(); +} + +bool DlcServiceChromeOS::GetDlcsToUpdate(vector<string>* dlc_ids) { + if (!dlc_ids) + return false; + dlc_ids->clear(); + + brillo::ErrorPtr err; + if (!GetDlcServiceProxy().GetDlcsToUpdate(dlc_ids, &err)) { + LOG(ERROR) << "dlcservice failed to return DLCs that need to be updated. " + << "ErrorCode=" << err->GetCode() + << ", ErrMsg=" << err->GetMessage(); + dlc_ids->clear(); + return false; + } + return true; +} + +bool DlcServiceChromeOS::InstallCompleted(const vector<string>& dlc_ids) { + brillo::ErrorPtr err; + if (!GetDlcServiceProxy().InstallCompleted(dlc_ids, &err)) { + LOG(ERROR) << "dlcservice failed to complete install. ErrCode=" + << err->GetCode() << ", ErrMsg=" << err->GetMessage(); + return false; + } + return true; +} + +bool DlcServiceChromeOS::UpdateCompleted(const vector<string>& dlc_ids) { + brillo::ErrorPtr err; + if (!GetDlcServiceProxy().UpdateCompleted(dlc_ids, &err)) { + LOG(ERROR) << "dlcservice failed to complete updated. ErrCode=" + << err->GetCode() << ", ErrMsg=" << err->GetMessage(); + return false; + } + return true; +} + +} // namespace chromeos_update_engine diff --git a/dlcservice_chromeos.h b/cros/dlcservice_chromeos.h index 8d103c13..3f11b126 100644 --- a/dlcservice_chromeos.h +++ b/cros/dlcservice_chromeos.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_DLCSERVICE_CHROMEOS_H_ -#define UPDATE_ENGINE_DLCSERVICE_CHROMEOS_H_ +#ifndef UPDATE_ENGINE_CROS_DLCSERVICE_CHROMEOS_H_ +#define UPDATE_ENGINE_CROS_DLCSERVICE_CHROMEOS_H_ #include <memory> #include <string> @@ -32,8 +32,19 @@ class DlcServiceChromeOS : public DlcServiceInterface { DlcServiceChromeOS() = default; ~DlcServiceChromeOS() = default; - // BootControlInterface overrides. - bool GetInstalled(std::vector<std::string>* dlc_module_ids) override; + // DlcServiceInterface overrides. + + // Will clear the |dlc_ids|, passed to be modified. Clearing by default has + // the added benefit of avoiding indeterminate behavior in the case that + // |dlc_ids| wasn't empty to begin which would lead to possible duplicates and + // cases when error was not checked it's still safe. + bool GetDlcsToUpdate(std::vector<std::string>* dlc_ids) override; + + // Call into dlcservice for it to mark the DLC IDs as being installed. + bool InstallCompleted(const std::vector<std::string>& dlc_ids) override; + + // Call into dlcservice for it to mark the DLC IDs as being updated. + bool UpdateCompleted(const std::vector<std::string>& dlc_ids) override; private: DISALLOW_COPY_AND_ASSIGN(DlcServiceChromeOS); @@ -41,4 +52,4 @@ class DlcServiceChromeOS : public DlcServiceInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_DLCSERVICE_CHROMEOS_H_ +#endif // UPDATE_ENGINE_CROS_DLCSERVICE_CHROMEOS_H_ diff --git a/payload_consumer/download_action.cc b/cros/download_action_chromeos.cc index 09afc42c..ee9c9a70 100644 --- a/payload_consumer/download_action.cc +++ b/cros/download_action_chromeos.cc @@ -1,5 +1,5 @@ // -// Copyright (C) 2011 The Android Open Source Project +// 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. @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/payload_consumer/download_action.h" +#include "update_engine/cros/download_action_chromeos.h" #include <errno.h> @@ -29,41 +29,37 @@ #include "update_engine/common/boot_control_interface.h" #include "update_engine/common/error_code_utils.h" #include "update_engine/common/multi_range_http_fetcher.h" +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" -#include "update_engine/omaha_request_params.h" -#include "update_engine/p2p_manager.h" -#include "update_engine/payload_state_interface.h" +#include "update_engine/cros/omaha_request_params.h" +#include "update_engine/cros/p2p_manager.h" +#include "update_engine/cros/payload_state_interface.h" using base::FilePath; using std::string; namespace chromeos_update_engine { -DownloadAction::DownloadAction(PrefsInterface* prefs, - BootControlInterface* boot_control, - HardwareInterface* hardware, - SystemState* system_state, - HttpFetcher* http_fetcher, - bool interactive) +DownloadActionChromeos::DownloadActionChromeos( + PrefsInterface* prefs, + BootControlInterface* boot_control, + HardwareInterface* hardware, + HttpFetcher* http_fetcher, + bool interactive) : prefs_(prefs), boot_control_(boot_control), hardware_(hardware), - system_state_(system_state), http_fetcher_(new MultiRangeHttpFetcher(http_fetcher)), interactive_(interactive), writer_(nullptr), code_(ErrorCode::kSuccess), delegate_(nullptr), p2p_sharing_fd_(-1), - p2p_visible_(true) { -#if BASE_VER < 576279 - base::StatisticsRecorder::Initialize(); -#endif -} + p2p_visible_(true) {} -DownloadAction::~DownloadAction() {} +DownloadActionChromeos::~DownloadActionChromeos() {} -void DownloadAction::CloseP2PSharingFd(bool delete_p2p_file) { +void DownloadActionChromeos::CloseP2PSharingFd(bool delete_p2p_file) { if (p2p_sharing_fd_ != -1) { if (close(p2p_sharing_fd_) != 0) { PLOG(ERROR) << "Error closing p2p sharing fd"; @@ -72,7 +68,8 @@ void DownloadAction::CloseP2PSharingFd(bool delete_p2p_file) { } if (delete_p2p_file) { - FilePath path = system_state_->p2p_manager()->FileGetPath(p2p_file_id_); + FilePath path = + SystemState::Get()->p2p_manager()->FileGetPath(p2p_file_id_); if (unlink(path.value().c_str()) != 0) { PLOG(ERROR) << "Error deleting p2p file " << path.value(); } else { @@ -84,8 +81,8 @@ void DownloadAction::CloseP2PSharingFd(bool delete_p2p_file) { p2p_file_id_.clear(); } -bool DownloadAction::SetupP2PSharingFd() { - P2PManager* p2p_manager = system_state_->p2p_manager(); +bool DownloadActionChromeos::SetupP2PSharingFd() { + P2PManager* p2p_manager = SystemState::Get()->p2p_manager(); if (!p2p_manager->FileShare(p2p_file_id_, payload_->size)) { LOG(ERROR) << "Unable to share file via p2p"; @@ -120,9 +117,9 @@ bool DownloadAction::SetupP2PSharingFd() { return true; } -void DownloadAction::WriteToP2PFile(const void* data, - size_t length, - off_t file_offset) { +void DownloadActionChromeos::WriteToP2PFile(const void* data, + size_t length, + off_t file_offset) { if (p2p_sharing_fd_ == -1) { if (!SetupP2PSharingFd()) return; @@ -167,7 +164,7 @@ void DownloadAction::WriteToP2PFile(const void* data, } } -void DownloadAction::PerformAction() { +void DownloadActionChromeos::PerformAction() { http_fetcher_->set_delegate(this); // Get the InstallPlan and read it @@ -206,18 +203,76 @@ void DownloadAction::PerformAction() { StartDownloading(); } -void DownloadAction::StartDownloading() { +bool DownloadActionChromeos::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 DownloadActionChromeos::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. @@ -241,20 +296,9 @@ 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(); + if (SystemState::Get() != nullptr) { + const PayloadStateInterface* payload_state = + SystemState::Get()->payload_state(); string file_id = utils::CalculateP2PFileId(payload_->hash, payload_->size); if (payload_state->GetUsingP2PForSharing()) { // If we're sharing the update, store the file_id to convey @@ -267,7 +311,7 @@ void DownloadAction::StartDownloading() { // hash. If this is the case, we NEED to clean it up otherwise // we're essentially timing out other peers downloading from us // (since we're never going to complete the file). - FilePath path = system_state_->p2p_manager()->FileGetPath(file_id); + FilePath path = SystemState::Get()->p2p_manager()->FileGetPath(file_id); if (!path.empty()) { if (unlink(path.value().c_str()) != 0) { PLOG(ERROR) << "Error deleting p2p file " << path.value(); @@ -293,15 +337,15 @@ void DownloadAction::StartDownloading() { http_fetcher_->BeginTransfer(install_plan_.download_url); } -void DownloadAction::SuspendAction() { +void DownloadActionChromeos::SuspendAction() { http_fetcher_->Pause(); } -void DownloadAction::ResumeAction() { +void DownloadActionChromeos::ResumeAction() { http_fetcher_->Unpause(); } -void DownloadAction::TerminateProcessing() { +void DownloadActionChromeos::TerminateProcessing() { if (writer_) { writer_->Close(); writer_ = nullptr; @@ -313,13 +357,13 @@ void DownloadAction::TerminateProcessing() { http_fetcher_->TerminateTransfer(); } -void DownloadAction::SeekToOffset(off_t offset) { +void DownloadActionChromeos::SeekToOffset(off_t offset) { bytes_received_ = offset; } -bool DownloadAction::ReceivedBytes(HttpFetcher* fetcher, - const void* bytes, - size_t length) { +bool DownloadActionChromeos::ReceivedBytes(HttpFetcher* fetcher, + const void* bytes, + size_t length) { // Note that bytes_received_ is the current offset. if (!p2p_file_id_.empty()) { WriteToP2PFile(bytes, length, bytes_received_); @@ -349,16 +393,17 @@ bool DownloadAction::ReceivedBytes(HttpFetcher* fetcher, // Call p2p_manager_->FileMakeVisible() when we've successfully // verified the manifest! - if (!p2p_visible_ && system_state_ && delta_performer_.get() && + if (!p2p_visible_ && SystemState::Get() && delta_performer_.get() && delta_performer_->IsManifestValid()) { LOG(INFO) << "Manifest has been validated. Making p2p file visible."; - system_state_->p2p_manager()->FileMakeVisible(p2p_file_id_); + SystemState::Get()->p2p_manager()->FileMakeVisible(p2p_file_id_); p2p_visible_ = true; } return true; } -void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) { +void DownloadActionChromeos::TransferComplete(HttpFetcher* fetcher, + bool successful) { if (writer_) { LOG_IF(WARNING, writer_->Close() != 0) << "Error closing the writer."; if (delta_performer_.get() == writer_) { @@ -374,7 +419,7 @@ void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) { code = delta_performer_->VerifyPayload(payload_->hash, payload_->size); if (code == ErrorCode::kSuccess) { if (payload_ < &install_plan_.payloads.back() && - system_state_->payload_state()->NextPayload()) { + SystemState::Get()->payload_state()->NextPayload()) { LOG(INFO) << "Incrementing to next payload"; // No need to reset if this payload was already applied. if (delta_performer_ && !payload_->already_applied) @@ -383,7 +428,7 @@ void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) { bytes_received_previous_payloads_ += payload_->size; payload_++; install_plan_.download_url = - system_state_->payload_state()->GetCurrentUrl(); + SystemState::Get()->payload_state()->GetCurrentUrl(); StartDownloading(); return; } @@ -392,11 +437,9 @@ void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) { if (delegate_) delegate_->DownloadComplete(); - // Log UpdateEngine.DownloadAction.* histograms to help diagnose - // long-blocking operations. std::string histogram_output; - base::StatisticsRecorder::WriteGraph("UpdateEngine.DownloadAction.", - &histogram_output); + base::StatisticsRecorder::WriteGraph( + "UpdateEngine.DownloadActionChromeos.", &histogram_output); LOG(INFO) << histogram_output; } else { LOG(ERROR) << "Download of " << install_plan_.download_url @@ -413,7 +456,7 @@ void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) { processor_->ActionComplete(this, code); } -void DownloadAction::TransferTerminated(HttpFetcher* fetcher) { +void DownloadActionChromeos::TransferTerminated(HttpFetcher* fetcher) { if (code_ != ErrorCode::kSuccess) { processor_->ActionComplete(this, code_); } else if (payload_->already_applied) { diff --git a/cros/download_action_chromeos.h b/cros/download_action_chromeos.h new file mode 100644 index 00000000..068946a9 --- /dev/null +++ b/cros/download_action_chromeos.h @@ -0,0 +1,174 @@ +// +// 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_COMMON_DOWNLOAD_ACTION_CHROMEOS_H_ +#define UPDATE_ENGINE_COMMON_DOWNLOAD_ACTION_CHROMEOS_H_ + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <memory> +#include <string> + +#include "update_engine/common/action.h" +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/download_action.h" +#include "update_engine/common/http_fetcher.h" +#include "update_engine/common/multi_range_http_fetcher.h" +#include "update_engine/payload_consumer/delta_performer.h" +#include "update_engine/payload_consumer/install_plan.h" + +// The Download Action downloads a specified url to disk. The url should point +// to an update in a delta payload format. The payload will be piped into a +// DeltaPerformer that will apply the delta to the disk. + +namespace chromeos_update_engine { + +class PrefsInterface; + +class DownloadActionChromeos : public InstallPlanAction, + public HttpFetcherDelegate { + public: + static std::string StaticType() { return "DownloadActionChromeos"; } + + // Takes ownership of the passed in HttpFetcher. Useful for testing. + // A good calling pattern is: + // DownloadActionChromeos(prefs, boot_contol, hardware, system_state, + // new WhateverHttpFetcher, false); + DownloadActionChromeos(PrefsInterface* prefs, + BootControlInterface* boot_control, + HardwareInterface* hardware, + HttpFetcher* http_fetcher, + bool interactive); + ~DownloadActionChromeos() override; + + // InstallPlanAction overrides. + void PerformAction() override; + void SuspendAction() override; + void ResumeAction() override; + void TerminateProcessing() override; + std::string Type() const override { return StaticType(); } + + // Testing + void SetTestFileWriter(FileWriter* writer) { writer_ = writer; } + + int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); } + + // HttpFetcherDelegate methods (see http_fetcher.h) + bool ReceivedBytes(HttpFetcher* fetcher, + const void* bytes, + size_t length) override; + void SeekToOffset(off_t offset) override; + void TransferComplete(HttpFetcher* fetcher, bool successful) override; + void TransferTerminated(HttpFetcher* fetcher) override; + + DownloadActionDelegate* delegate() const { return delegate_; } + void set_delegate(DownloadActionDelegate* delegate) { delegate_ = delegate; } + + void set_base_offset(int64_t base_offset) { base_offset_ = base_offset; } + + HttpFetcher* http_fetcher() { return http_fetcher_.get(); } + + // Returns the p2p file id for the file being written or the empty + // string if we're not writing to a p2p file. + std::string p2p_file_id() { return p2p_file_id_; } + + private: + // Closes the file descriptor for the p2p file being written and + // clears |p2p_file_id_| to indicate that we're no longer sharing + // the file. If |delete_p2p_file| is True, also deletes the file. + // If there is no p2p file descriptor, this method does nothing. + void CloseP2PSharingFd(bool delete_p2p_file); + + // Starts sharing the p2p file. Must be called before + // WriteToP2PFile(). Returns True if this worked. + bool SetupP2PSharingFd(); + + // Writes |length| bytes of payload from |data| into |file_offset| + // 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(); + + // Pointer to the current payload in install_plan_.payloads. + InstallPlan::Payload* payload_{nullptr}; + + PrefsInterface* prefs_; + BootControlInterface* boot_control_; + HardwareInterface* hardware_; + + // Pointer to the MultiRangeHttpFetcher that does the http work. + std::unique_ptr<MultiRangeHttpFetcher> http_fetcher_; + + // If |true|, the update is user initiated (vs. periodic update checks). Hence + // the |delta_performer_| can decide not to use O_DSYNC flag for faster + // update. + bool interactive_; + + // The FileWriter that downloaded data should be written to. It will + // either point to *decompressing_file_writer_ or *delta_performer_. + FileWriter* writer_; + + std::unique_ptr<DeltaPerformer> delta_performer_; + + // Used by TransferTerminated to figure if this action terminated itself or + // was terminated by the action processor. + ErrorCode code_; + + // For reporting status to outsiders + DownloadActionDelegate* delegate_; + uint64_t bytes_received_{0}; // per file/range + uint64_t bytes_received_previous_payloads_{0}; + uint64_t bytes_total_{0}; + bool download_active_{false}; + + // The file-id for the file we're sharing or the empty string + // if we're not using p2p to share. + std::string p2p_file_id_; + + // The file descriptor for the p2p file used for caching the payload or -1 + // if we're not using p2p to share. + int p2p_sharing_fd_; + + // Set to |false| if p2p file is not visible. + bool p2p_visible_; + + // Loaded from prefs before downloading any payload. + size_t resume_payload_index_{0}; + + // Offset of the payload in the download URL, used by UpdateAttempterAndroid. + int64_t base_offset_{0}; + + DISALLOW_COPY_AND_ASSIGN(DownloadActionChromeos); +}; + +// We want to be sure that we're compiled with large file support on linux, +// just in case we find ourselves downloading large images. +static_assert(8 == sizeof(off_t), "off_t not 64 bit"); + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_DOWNLOAD_ACTION_CHROMEOS_H_ diff --git a/payload_consumer/download_action_unittest.cc b/cros/download_action_chromeos_unittest.cc index e6ca219b..93c39ff4 100644 --- a/payload_consumer/download_action_unittest.cc +++ b/cros/download_action_chromeos_unittest.cc @@ -14,8 +14,6 @@ // limitations under the License. // -#include "update_engine/payload_consumer/download_action.h" - #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -34,14 +32,14 @@ #include "update_engine/common/action_pipe.h" #include "update_engine/common/hash_calculator.h" +#include "update_engine/common/mock_download_action.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/common/utils.h" -#include "update_engine/fake_p2p_manager_configuration.h" -#include "update_engine/fake_system_state.h" -#include "update_engine/mock_file_writer.h" -#include "update_engine/payload_consumer/mock_download_action.h" +#include "update_engine/cros/download_action_chromeos.h" +#include "update_engine/cros/fake_p2p_manager_configuration.h" +#include "update_engine/cros/fake_system_state.h" +#include "update_engine/payload_consumer/mock_file_writer.h" #include "update_engine/update_manager/fake_update_manager.h" namespace chromeos_update_engine { @@ -51,14 +49,15 @@ using base::ReadFileToString; using base::WriteFile; using std::string; using std::unique_ptr; -using test_utils::ScopedTempFile; using testing::_; using testing::AtLeast; using testing::InSequence; using testing::Return; using testing::SetArgPointee; -class DownloadActionTest : public ::testing::Test {}; +class DownloadActionChromeosTest : public ::testing::Test { + void SetUp() { FakeSystemState::CreateInstance(); } +}; namespace { @@ -87,9 +86,10 @@ class DownloadActionTestProcessorDelegate : public ActionProcessorDelegate { AbstractAction* action, ErrorCode code) override { const string type = action->Type(); - if (type == DownloadAction::StaticType()) { + if (type == DownloadActionChromeos::StaticType()) { EXPECT_EQ(expected_code_, code); - p2p_file_id_ = static_cast<DownloadAction*>(action)->p2p_file_id(); + p2p_file_id_ = + static_cast<DownloadActionChromeos*>(action)->p2p_file_id(); } else { EXPECT_EQ(ErrorCode::kSuccess, code); } @@ -129,11 +129,10 @@ void StartProcessorInRunLoop(ActionProcessor* processor, void TestWithData(const brillo::Blob& data, int fail_write, bool use_download_delegate) { + FakeSystemState::CreateInstance(); brillo::FakeMessageLoop loop(nullptr); loop.SetAsCurrent(); - FakeSystemState fake_system_state; - // TODO(adlr): see if we need a different file for build bots ScopedTempFile output_temp_file; TestDirectFileWriter writer; EXPECT_EQ( @@ -151,9 +150,9 @@ void TestWithData(const brillo::Blob& data, install_plan.target_slot = 1; // We mark both slots as bootable. Only the target slot should be unbootable // after the download starts. - fake_system_state.fake_boot_control()->SetSlotBootable( + FakeSystemState::Get()->fake_boot_control()->SetSlotBootable( install_plan.source_slot, true); - fake_system_state.fake_boot_control()->SetSlotBootable( + FakeSystemState::Get()->fake_boot_control()->SetSlotBootable( install_plan.target_slot, true); auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>(); feeder_action->set_obj(install_plan); @@ -161,13 +160,12 @@ void TestWithData(const brillo::Blob& data, MockHttpFetcher* http_fetcher = new MockHttpFetcher(data.data(), data.size(), nullptr); // takes ownership of passed in HttpFetcher - auto download_action = - std::make_unique<DownloadAction>(&prefs, - fake_system_state.boot_control(), - fake_system_state.hardware(), - &fake_system_state, - http_fetcher, - false /* interactive */); + auto download_action = std::make_unique<DownloadActionChromeos>( + &prefs, + FakeSystemState::Get()->boot_control(), + FakeSystemState::Get()->hardware(), + http_fetcher, + false /* interactive */); download_action->SetTestFileWriter(&writer); BondActions(feeder_action.get(), download_action.get()); MockDownloadActionDelegate download_delegate; @@ -196,9 +194,9 @@ void TestWithData(const brillo::Blob& data, loop.Run(); EXPECT_FALSE(loop.PendingTasks()); - EXPECT_TRUE(fake_system_state.fake_boot_control()->IsSlotBootable( + EXPECT_TRUE(FakeSystemState::Get()->fake_boot_control()->IsSlotBootable( install_plan.source_slot)); - EXPECT_FALSE(fake_system_state.fake_boot_control()->IsSlotBootable( + EXPECT_FALSE(FakeSystemState::Get()->fake_boot_control()->IsSlotBootable( install_plan.target_slot)); } } // namespace @@ -253,8 +251,7 @@ TEST(DownloadActionTest, MultiPayloadProgressTest) { payload_datas.emplace_back(2 * kMockHttpFetcherChunkSize); brillo::FakeMessageLoop loop(nullptr); loop.SetAsCurrent(); - FakeSystemState fake_system_state; - EXPECT_CALL(*fake_system_state.mock_payload_state(), NextPayload()) + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), NextPayload()) .WillOnce(Return(true)); MockFileWriter mock_file_writer; @@ -277,13 +274,12 @@ TEST(DownloadActionTest, MultiPayloadProgressTest) { MockHttpFetcher* http_fetcher = new MockHttpFetcher( payload_datas[0].data(), payload_datas[0].size(), nullptr); // takes ownership of passed in HttpFetcher - auto download_action = - std::make_unique<DownloadAction>(&prefs, - fake_system_state.boot_control(), - fake_system_state.hardware(), - &fake_system_state, - http_fetcher, - false /* interactive */); + auto download_action = std::make_unique<DownloadActionChromeos>( + &prefs, + FakeSystemState::Get()->boot_control(), + FakeSystemState::Get()->hardware(), + http_fetcher, + false /* interactive */); download_action->SetTestFileWriter(&mock_file_writer); BondActions(feeder_action.get(), download_action.get()); MockDownloadActionDelegate download_delegate; @@ -348,6 +344,7 @@ void TerminateEarlyTestStarter(ActionProcessor* processor) { } void TestTerminateEarly(bool use_download_delegate) { + FakeSystemState::CreateInstance(); brillo::FakeMessageLoop loop(nullptr); loop.SetAsCurrent(); @@ -364,13 +361,12 @@ void TestTerminateEarly(bool use_download_delegate) { InstallPlan install_plan; install_plan.payloads.resize(1); feeder_action->set_obj(install_plan); - FakeSystemState fake_system_state_; + MockPrefs prefs; auto download_action = std::make_unique<DownloadAction>( &prefs, - fake_system_state_.boot_control(), - fake_system_state_.hardware(), - &fake_system_state_, + FakeSystemState::Get()->boot_control(), + FakeSystemState::Get()->hardware(), new MockHttpFetcher(data.data(), data.size(), nullptr), false /* interactive */); download_action->SetTestFileWriter(&writer); @@ -463,6 +459,7 @@ class PassObjectOutTestProcessorDelegate : public ActionProcessorDelegate { } // namespace TEST(DownloadActionTest, PassObjectOutTest) { + FakeSystemState::CreateInstance(); brillo::FakeMessageLoop loop(nullptr); loop.SetAsCurrent(); @@ -477,12 +474,10 @@ TEST(DownloadActionTest, PassObjectOutTest) { auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>(); feeder_action->set_obj(install_plan); MockPrefs prefs; - FakeSystemState fake_system_state_; auto download_action = std::make_unique<DownloadAction>(&prefs, - fake_system_state_.boot_control(), - fake_system_state_.hardware(), - &fake_system_state_, + FakeSystemState::Get()->boot_control(), + FakeSystemState::Get()->hardware(), new MockHttpFetcher("x", 1, nullptr), false /* interactive */); download_action->SetTestFileWriter(&writer); @@ -513,13 +508,15 @@ TEST(DownloadActionTest, PassObjectOutTest) { // Test fixture for P2P tests. class P2PDownloadActionTest : public testing::Test { protected: - P2PDownloadActionTest() - : start_at_offset_(0), fake_um_(fake_system_state_.fake_clock()) {} + P2PDownloadActionTest() : start_at_offset_(0) {} ~P2PDownloadActionTest() override {} // Derived from testing::Test. - void SetUp() override { loop_.SetAsCurrent(); } + void SetUp() override { + loop_.SetAsCurrent(); + FakeSystemState::CreateInstance(); + } // Derived from testing::Test. void TearDown() override { EXPECT_FALSE(loop_.PendingTasks()); } @@ -535,20 +532,15 @@ class P2PDownloadActionTest : public testing::Test { // Setup p2p. FakeP2PManagerConfiguration* test_conf = new FakeP2PManagerConfiguration(); - p2p_manager_.reset(P2PManager::Construct(test_conf, - nullptr, - &fake_um_, - "cros_au", - 3, - base::TimeDelta::FromDays(5))); - fake_system_state_.set_p2p_manager(p2p_manager_.get()); + p2p_manager_.reset(P2PManager::Construct( + test_conf, &fake_um_, "cros_au", 3, base::TimeDelta::FromDays(5))); } // To be called by tests to perform the download. The // |use_p2p_to_share| parameter is used to indicate whether the // payload should be shared via p2p. void StartDownload(bool use_p2p_to_share) { - EXPECT_CALL(*fake_system_state_.mock_payload_state(), + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), GetUsingP2PForSharing()) .WillRepeatedly(Return(use_p2p_to_share)); @@ -566,9 +558,8 @@ class P2PDownloadActionTest : public testing::Test { // Note that DownloadAction takes ownership of the passed in HttpFetcher. auto download_action = std::make_unique<DownloadAction>( &prefs, - fake_system_state_.boot_control(), - fake_system_state_.hardware(), - &fake_system_state_, + FakeSystemState::Get()->boot_control(), + FakeSystemState::Get()->hardware(), new MockHttpFetcher(data_.c_str(), data_.length(), nullptr), false /* interactive */); auto http_fetcher = download_action->http_fetcher(); @@ -605,9 +596,6 @@ class P2PDownloadActionTest : public testing::Test { // The ActionProcessor used for running the actions. ActionProcessor processor_; - // A fake system state. - FakeSystemState fake_system_state_; - // The data being downloaded. string data_; diff --git a/cros/excluder_chromeos.cc b/cros/excluder_chromeos.cc new file mode 100644 index 00000000..35154d6a --- /dev/null +++ b/cros/excluder_chromeos.cc @@ -0,0 +1,63 @@ +// +// 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/cros/excluder_chromeos.h" + +#include <memory> +#include <vector> + +#include <base/logging.h> +#include <base/strings/string_piece.h> +#include <base/strings/string_number_conversions.h> +#include <base/strings/string_split.h> + +#include "update_engine/common/constants.h" +#include "update_engine/common/system_state.h" + +using std::string; +using std::vector; + +namespace chromeos_update_engine { + +std::unique_ptr<ExcluderInterface> CreateExcluder() { + return std::make_unique<ExcluderChromeOS>(); +} + +bool ExcluderChromeOS::Exclude(const string& name) { + auto* prefs = SystemState::Get()->prefs(); + auto key = prefs->CreateSubKey({kExclusionPrefsSubDir, name}); + return prefs->SetString(key, ""); +} + +bool ExcluderChromeOS::IsExcluded(const string& name) { + auto* prefs = SystemState::Get()->prefs(); + auto key = prefs->CreateSubKey({kExclusionPrefsSubDir, name}); + return prefs->Exists(key); +} + +bool ExcluderChromeOS::Reset() { + auto* prefs = SystemState::Get()->prefs(); + bool ret = true; + vector<string> keys; + if (!prefs->GetSubKeys(kExclusionPrefsSubDir, &keys)) + return false; + for (const auto& key : keys) + if (!(ret &= prefs->Delete(key))) + LOG(ERROR) << "Failed to delete exclusion pref for " << key; + return ret; +} + +} // namespace chromeos_update_engine diff --git a/cros/excluder_chromeos.h b/cros/excluder_chromeos.h new file mode 100644 index 00000000..7d3efc9e --- /dev/null +++ b/cros/excluder_chromeos.h @@ -0,0 +1,49 @@ +// +// 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_CROS_EXCLUDER_CHROMEOS_H_ +#define UPDATE_ENGINE_CROS_EXCLUDER_CHROMEOS_H_ + +#include <string> + +#include "update_engine/common/excluder_interface.h" +#include "update_engine/common/prefs_interface.h" + +namespace chromeos_update_engine { + +class SystemState; + +// The Chrome OS implementation of the |ExcluderInterface|. +class ExcluderChromeOS : public ExcluderInterface { + public: + ExcluderChromeOS() = default; + ~ExcluderChromeOS() = default; + + // |ExcluderInterface| overrides. + bool Exclude(const std::string& name) override; + bool IsExcluded(const std::string& name) override; + bool Reset() override; + + // Not copyable or movable. + ExcluderChromeOS(const ExcluderChromeOS&) = delete; + ExcluderChromeOS& operator=(const ExcluderChromeOS&) = delete; + ExcluderChromeOS(ExcluderChromeOS&&) = delete; + ExcluderChromeOS& operator=(ExcluderChromeOS&&) = delete; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_CROS_EXCLUDER_CHROMEOS_H_ diff --git a/cros/excluder_chromeos_unittest.cc b/cros/excluder_chromeos_unittest.cc new file mode 100644 index 00000000..fd708186 --- /dev/null +++ b/cros/excluder_chromeos_unittest.cc @@ -0,0 +1,54 @@ +// +// 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/cros/excluder_chromeos.h" + +#include <gtest/gtest.h> + +#include "update_engine/cros/fake_system_state.h" + +namespace chromeos_update_engine { + +namespace { +constexpr char kFakeHash[] = + "71ff43d76e2488e394e46872f5b066cc25e394c2c3e3790dd319517883b33db1"; +} // namespace + +class ExcluderChromeOSTest : public ::testing::Test { + protected: + void SetUp() override { FakeSystemState::CreateInstance(); } + + ExcluderChromeOS excluder_; +}; + +TEST_F(ExcluderChromeOSTest, ExclusionCheck) { + 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(kFakeHash)); + EXPECT_TRUE(excluder_.IsExcluded("abc")); + EXPECT_TRUE(excluder_.IsExcluded(kFakeHash)); + + EXPECT_TRUE(excluder_.Reset()); + EXPECT_FALSE(excluder_.IsExcluded("abc")); + EXPECT_FALSE(excluder_.IsExcluded(kFakeHash)); +} + +} // namespace chromeos_update_engine diff --git a/fake_p2p_manager.h b/cros/fake_p2p_manager.h index 1f8ae95f..1011b7ed 100644 --- a/fake_p2p_manager.h +++ b/cros/fake_p2p_manager.h @@ -14,12 +14,12 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_FAKE_P2P_MANAGER_H_ -#define UPDATE_ENGINE_FAKE_P2P_MANAGER_H_ +#ifndef UPDATE_ENGINE_CROS_FAKE_P2P_MANAGER_H_ +#define UPDATE_ENGINE_CROS_FAKE_P2P_MANAGER_H_ #include <string> -#include "update_engine/p2p_manager.h" +#include "update_engine/cros/p2p_manager.h" namespace chromeos_update_engine { @@ -109,4 +109,4 @@ class FakeP2PManager : public P2PManager { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_FAKE_P2P_MANAGER_H_ +#endif // UPDATE_ENGINE_CROS_FAKE_P2P_MANAGER_H_ diff --git a/fake_p2p_manager_configuration.h b/cros/fake_p2p_manager_configuration.h index f5b0e80d..8d50ac85 100644 --- a/fake_p2p_manager_configuration.h +++ b/cros/fake_p2p_manager_configuration.h @@ -14,10 +14,10 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H_ -#define UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H_ +#ifndef UPDATE_ENGINE_CROS_FAKE_P2P_MANAGER_CONFIGURATION_H_ +#define UPDATE_ENGINE_CROS_FAKE_P2P_MANAGER_CONFIGURATION_H_ -#include "update_engine/p2p_manager.h" +#include "update_engine/cros/p2p_manager.h" #include <string> #include <vector> @@ -99,4 +99,4 @@ class FakeP2PManagerConfiguration : public P2PManager::Configuration { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H_ +#endif // UPDATE_ENGINE_CROS_FAKE_P2P_MANAGER_CONFIGURATION_H_ diff --git a/fake_shill_proxy.cc b/cros/fake_shill_proxy.cc index de965119..2d05a6b1 100644 --- a/fake_shill_proxy.cc +++ b/cros/fake_shill_proxy.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/fake_shill_proxy.h" +#include "update_engine/cros/fake_shill_proxy.h" #include <utility> diff --git a/fake_shill_proxy.h b/cros/fake_shill_proxy.h index ae17eaa3..8c15a9dc 100644 --- a/fake_shill_proxy.h +++ b/cros/fake_shill_proxy.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_FAKE_SHILL_PROXY_H_ -#define UPDATE_ENGINE_FAKE_SHILL_PROXY_H_ +#ifndef UPDATE_ENGINE_CROS_FAKE_SHILL_PROXY_H_ +#define UPDATE_ENGINE_CROS_FAKE_SHILL_PROXY_H_ #include <map> #include <memory> @@ -25,7 +25,7 @@ #include <shill/dbus-proxies.h> #include <shill/dbus-proxy-mocks.h> -#include "update_engine/shill_proxy_interface.h" +#include "update_engine/cros/shill_proxy_interface.h" namespace chromeos_update_engine { @@ -63,4 +63,4 @@ class FakeShillProxy : public ShillProxyInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_FAKE_SHILL_PROXY_H_ +#endif // UPDATE_ENGINE_CROS_FAKE_SHILL_PROXY_H_ diff --git a/fake_system_state.cc b/cros/fake_system_state.cc index 1bfcafa1..7673b1de 100644 --- a/fake_system_state.cc +++ b/cros/fake_system_state.cc @@ -14,22 +14,20 @@ // limitations under the License. // -#include "update_engine/fake_system_state.h" +#include "update_engine/cros/fake_system_state.h" namespace chromeos_update_engine { // Mock the SystemStateInterface so that we could lie that // OOBE is completed even when there's no such marker file, etc. FakeSystemState::FakeSystemState() - : mock_update_attempter_(this, nullptr), - mock_request_params_(this), - fake_update_manager_(&fake_clock_), + : mock_update_attempter_(nullptr), clock_(&fake_clock_), connection_manager_(&mock_connection_manager_), hardware_(&fake_hardware_), metrics_reporter_(&mock_metrics_reporter_), - prefs_(&mock_prefs_), - powerwash_safe_prefs_(&mock_powerwash_safe_prefs_), + prefs_(&fake_prefs_), + powerwash_safe_prefs_(&fake_powerwash_safe_prefs_), payload_state_(&mock_payload_state_), update_attempter_(&mock_update_attempter_), request_params_(&mock_request_params_), @@ -37,7 +35,7 @@ FakeSystemState::FakeSystemState() update_manager_(&fake_update_manager_), device_policy_(nullptr), fake_system_rebooted_(false) { - mock_payload_state_.Initialize(this); + mock_payload_state_.Initialize(); } } // namespace chromeos_update_engine diff --git a/fake_system_state.h b/cros/fake_system_state.h index 24b1eec2..da36306a 100644 --- a/fake_system_state.h +++ b/cros/fake_system_state.h @@ -14,8 +14,10 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_FAKE_SYSTEM_STATE_H_ -#define UPDATE_ENGINE_FAKE_SYSTEM_STATE_H_ +#ifndef UPDATE_ENGINE_CROS_FAKE_SYSTEM_STATE_H_ +#define UPDATE_ENGINE_CROS_FAKE_SYSTEM_STATE_H_ + +#include <memory> #include <base/logging.h> #include <gmock/gmock.h> @@ -25,15 +27,16 @@ #include "update_engine/common/fake_boot_control.h" #include "update_engine/common/fake_clock.h" #include "update_engine/common/fake_hardware.h" +#include "update_engine/common/fake_prefs.h" +#include "update_engine/common/mock_metrics_reporter.h" #include "update_engine/common/mock_prefs.h" -#include "update_engine/mock_connection_manager.h" -#include "update_engine/mock_metrics_reporter.h" -#include "update_engine/mock_omaha_request_params.h" -#include "update_engine/mock_p2p_manager.h" -#include "update_engine/mock_payload_state.h" -#include "update_engine/mock_power_manager.h" -#include "update_engine/mock_update_attempter.h" -#include "update_engine/system_state.h" +#include "update_engine/common/system_state.h" +#include "update_engine/cros/mock_connection_manager.h" +#include "update_engine/cros/mock_omaha_request_params.h" +#include "update_engine/cros/mock_p2p_manager.h" +#include "update_engine/cros/mock_payload_state.h" +#include "update_engine/cros/mock_power_manager.h" +#include "update_engine/cros/mock_update_attempter.h" #include "update_engine/update_manager/fake_update_manager.h" namespace chromeos_update_engine { @@ -42,7 +45,15 @@ namespace chromeos_update_engine { // OOBE is completed even when there's no such marker file, etc. class FakeSystemState : public SystemState { public: - FakeSystemState(); + static void CreateInstance() { + static std::unique_ptr<FakeSystemState> system_state; + system_state.reset(new FakeSystemState()); + g_pointer_ = system_state.get(); + } + + static FakeSystemState* Get() { + return reinterpret_cast<FakeSystemState*>(g_pointer_); + } // Base class overrides. All getters return the current implementation of // various members, either the default (fake/mock) or the one set to override @@ -196,6 +207,16 @@ class FakeSystemState : public SystemState { return &fake_hardware_; } + inline FakePrefs* fake_prefs() { + CHECK(prefs_ == &fake_prefs_); + return &fake_prefs_; + } + + inline FakePrefs* fake_powerwash_safe_prefs() { + CHECK(powerwash_safe_prefs_ == &fake_powerwash_safe_prefs_); + return &fake_powerwash_safe_prefs_; + } + inline testing::NiceMock<MockMetricsReporter>* mock_metrics_reporter() { CHECK(metrics_reporter_ == &mock_metrics_reporter_); return &mock_metrics_reporter_; @@ -237,11 +258,18 @@ class FakeSystemState : public SystemState { } private: + // Don't allow for direct initialization of this class. + FakeSystemState(); + // Default mock/fake implementations (owned). + chromeos_update_manager::FakeUpdateManager fake_update_manager_; FakeBootControl fake_boot_control_; FakeClock fake_clock_; - testing::NiceMock<MockConnectionManager> mock_connection_manager_; FakeHardware fake_hardware_; + FakePrefs fake_prefs_; + FakePrefs fake_powerwash_safe_prefs_; + + testing::NiceMock<MockConnectionManager> mock_connection_manager_; testing::NiceMock<MockMetricsReporter> mock_metrics_reporter_; testing::NiceMock<MockPrefs> mock_prefs_; testing::NiceMock<MockPrefs> mock_powerwash_safe_prefs_; @@ -249,7 +277,6 @@ class FakeSystemState : public SystemState { testing::NiceMock<MockUpdateAttempter> mock_update_attempter_; testing::NiceMock<MockOmahaRequestParams> mock_request_params_; testing::NiceMock<MockP2PManager> mock_p2p_manager_; - chromeos_update_manager::FakeUpdateManager fake_update_manager_; testing::NiceMock<MockPowerManager> mock_power_manager_; // Pointers to objects that client code can override. They are initialized to @@ -278,4 +305,4 @@ class FakeSystemState : public SystemState { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_FAKE_SYSTEM_STATE_H_ +#endif // UPDATE_ENGINE_CROS_FAKE_SYSTEM_STATE_H_ diff --git a/hardware_chromeos.cc b/cros/hardware_chromeos.cc index a49375e6..ad0a64dc 100644 --- a/hardware_chromeos.cc +++ b/cros/hardware_chromeos.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/hardware_chromeos.h" +#include "update_engine/cros/hardware_chromeos.h" #include <utility> @@ -37,7 +37,10 @@ extern "C" { #include "update_engine/common/platform_constants.h" #include "update_engine/common/subprocess.h" #include "update_engine/common/utils.h" -#include "update_engine/dbus_connection.h" +#include "update_engine/cros/dbus_connection.h" +#if USE_CFM +#include "update_engine/cros/requisition_util.h" +#endif using std::string; using std::vector; @@ -47,7 +50,7 @@ namespace { const char kOOBECompletedMarker[] = "/home/chronos/.oobe_completed"; // The stateful directory used by update_engine to store powerwash-safe files. -// The files stored here must be whitelisted in the powerwash scripts. +// The files stored here must be added to the powerwash script allowlist. const char kPowerwashSafeDirectory[] = "/mnt/stateful_partition/unencrypted/preserve"; @@ -61,6 +64,11 @@ const char kPowerwashCountMarker[] = "powerwash_count"; const char kPowerwashMarkerFile[] = "/mnt/stateful_partition/factory_install_reset"; +// The name of the marker file used to trigger a save of rollback data +// during the next shutdown. +const char kRollbackSaveMarkerFile[] = + "/mnt/stateful_partition/.save_rollback_data"; + // The contents of the powerwash marker file for the non-rollback case. const char kPowerwashCommand[] = "safe fast keepimg reason=update_engine\n"; @@ -167,22 +175,13 @@ string HardwareChromeOS::GetHardwareClass() const { return ReadValueFromCrosSystem("hwid"); } -string HardwareChromeOS::GetFirmwareVersion() const { - return ReadValueFromCrosSystem("fwid"); -} - -string HardwareChromeOS::GetECVersion() const { - string input_line; - int exit_code = 0; - vector<string> cmd = {"/usr/sbin/mosys", "-k", "ec", "info"}; - - bool success = Subprocess::SynchronousExec(cmd, &exit_code, &input_line); - if (!success || exit_code) { - LOG(ERROR) << "Unable to read ec info from mosys (" << exit_code << ")"; - return ""; - } - - return utils::ParseECVersion(input_line); +string HardwareChromeOS::GetDeviceRequisition() const { +#if USE_CFM + const char* kLocalStatePath = "/home/chronos/Local State"; + return ReadDeviceRequisition(base::FilePath(kLocalStatePath)); +#else + return ""; +#endif } int HardwareChromeOS::GetMinKernelKeyVersion() const { @@ -226,15 +225,25 @@ int HardwareChromeOS::GetPowerwashCount() const { return powerwash_count; } -bool HardwareChromeOS::SchedulePowerwash(bool is_rollback) { +bool HardwareChromeOS::SchedulePowerwash(bool save_rollback_data) { + if (save_rollback_data) { + if (!utils::WriteFile(kRollbackSaveMarkerFile, nullptr, 0)) { + PLOG(ERROR) << "Error in creating rollback save marker file: " + << kRollbackSaveMarkerFile << ". Rollback will not" + << " preserve any data."; + } else { + LOG(INFO) << "Rollback data save has been scheduled on next shutdown."; + } + } + const char* powerwash_command = - is_rollback ? kRollbackPowerwashCommand : kPowerwashCommand; + save_rollback_data ? kRollbackPowerwashCommand : kPowerwashCommand; bool result = utils::WriteFile( kPowerwashMarkerFile, powerwash_command, strlen(powerwash_command)); if (result) { LOG(INFO) << "Created " << kPowerwashMarkerFile - << " to powerwash on next reboot (is_rollback=" << is_rollback - << ")"; + << " to powerwash on next reboot (" + << "save_rollback_data=" << save_rollback_data << ")"; } else { PLOG(ERROR) << "Error in creating powerwash marker file: " << kPowerwashMarkerFile; @@ -244,7 +253,7 @@ bool HardwareChromeOS::SchedulePowerwash(bool is_rollback) { } bool HardwareChromeOS::CancelPowerwash() { - bool result = base::DeleteFile(base::FilePath(kPowerwashMarkerFile), false); + bool result = base::DeleteFile(base::FilePath(kPowerwashMarkerFile)); if (result) { LOG(INFO) << "Successfully deleted the powerwash marker file : " @@ -254,6 +263,11 @@ bool HardwareChromeOS::CancelPowerwash() { << kPowerwashMarkerFile; } + // Delete the rollback save marker file if it existed. + if (!base::DeleteFile(base::FilePath(kRollbackSaveMarkerFile))) { + PLOG(ERROR) << "Could not remove rollback save marker"; + } + return result; } @@ -291,17 +305,11 @@ void HardwareChromeOS::LoadConfig(const string& root_prefix, bool normal_mode) { } bool HardwareChromeOS::GetFirstActiveOmahaPingSent() const { - int exit_code = 0; string active_ping_str; - vector<string> cmd = {"vpd_get_value", kActivePingKey}; - if (!Subprocess::SynchronousExec(cmd, &exit_code, &active_ping_str) || - exit_code) { - LOG(ERROR) << "Failed to get vpd key for " << kActivePingKey - << " with exit code: " << exit_code; + if (!utils::GetVpdValue(kActivePingKey, &active_ping_str)) { return false; } - base::TrimWhitespaceASCII(active_ping_str, base::TRIM_ALL, &active_ping_str); int active_ping; if (active_ping_str.empty() || !base::StringToInt(active_ping_str, &active_ping)) { @@ -313,26 +321,51 @@ bool HardwareChromeOS::GetFirstActiveOmahaPingSent() const { bool HardwareChromeOS::SetFirstActiveOmahaPingSent() { int exit_code = 0; - string output; + string output, error; vector<string> vpd_set_cmd = { "vpd", "-i", "RW_VPD", "-s", string(kActivePingKey) + "=1"}; - if (!Subprocess::SynchronousExec(vpd_set_cmd, &exit_code, &output) || + if (!Subprocess::SynchronousExec(vpd_set_cmd, &exit_code, &output, &error) || exit_code) { LOG(ERROR) << "Failed to set vpd key for " << kActivePingKey - << " with exit code: " << exit_code << " with error: " << output; + << " with exit code: " << exit_code << " with output: " << output + << " and error: " << error; return false; + } else if (!error.empty()) { + LOG(INFO) << "vpd succeeded but with error logs: " << error; } vector<string> vpd_dump_cmd = {"dump_vpd_log", "--force"}; - if (!Subprocess::SynchronousExec(vpd_dump_cmd, &exit_code, &output) || + if (!Subprocess::SynchronousExec(vpd_dump_cmd, &exit_code, &output, &error) || exit_code) { LOG(ERROR) << "Failed to cache " << kActivePingKey << " using dump_vpd_log" - << " with exit code: " << exit_code << " with error: " << output; + << " with exit code: " << exit_code << " with output: " << output + << " and error: " << error; return false; + } else if (!error.empty()) { + LOG(INFO) << "dump_vpd_log succeeded but with error logs: " << error; } return true; } void HardwareChromeOS::SetWarmReset(bool warm_reset) {} +void HardwareChromeOS::SetVbmetaDigestForInactiveSlot(bool 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; +} + +const char* HardwareChromeOS::GetPartitionMountOptions( + const std::string& partition_name) const { + return ""; +} + } // namespace chromeos_update_engine diff --git a/hardware_chromeos.h b/cros/hardware_chromeos.h index 2bea9896..a64f8049 100644 --- a/hardware_chromeos.h +++ b/cros/hardware_chromeos.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_HARDWARE_CHROMEOS_H_ -#define UPDATE_ENGINE_HARDWARE_CHROMEOS_H_ +#ifndef UPDATE_ENGINE_CROS_HARDWARE_CHROMEOS_H_ +#define UPDATE_ENGINE_CROS_HARDWARE_CHROMEOS_H_ #include <memory> #include <string> @@ -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 { @@ -45,15 +46,14 @@ class HardwareChromeOS final : public HardwareInterface { bool IsOOBEEnabled() const override; bool IsOOBEComplete(base::Time* out_time_of_oobe) const override; std::string GetHardwareClass() const override; - std::string GetFirmwareVersion() const override; - std::string GetECVersion() const override; + std::string GetDeviceRequisition() const override; int GetMinKernelKeyVersion() const override; int GetMinFirmwareKeyVersion() const override; int GetMaxFirmwareKeyRollforward() const override; bool SetMaxFirmwareKeyRollforward(int firmware_max_rollforward) override; bool SetMaxKernelKeyRollforward(int kernel_max_rollforward) override; int GetPowerwashCount() const override; - bool SchedulePowerwash(bool is_rollback) override; + bool SchedulePowerwash(bool save_rollback_data) override; bool CancelPowerwash() override; bool GetNonVolatileDirectory(base::FilePath* path) const override; bool GetPowerwashSafeDirectory(base::FilePath* path) const override; @@ -62,6 +62,14 @@ class HardwareChromeOS final : public HardwareInterface { bool GetFirstActiveOmahaPingSent() const override; bool SetFirstActiveOmahaPingSent() override; void SetWarmReset(bool warm_reset) override; + void SetVbmetaDigestForInactiveSlot(bool 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; + const char* GetPartitionMountOptions( + const std::string& partition_name) const override; private: friend class HardwareChromeOSTest; @@ -80,4 +88,4 @@ class HardwareChromeOS final : public HardwareInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_HARDWARE_CHROMEOS_H_ +#endif // UPDATE_ENGINE_CROS_HARDWARE_CHROMEOS_H_ diff --git a/hardware_chromeos_unittest.cc b/cros/hardware_chromeos_unittest.cc index 162dec4b..50bced6e 100644 --- a/hardware_chromeos_unittest.cc +++ b/cros/hardware_chromeos_unittest.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/hardware_chromeos.h" +#include "update_engine/cros/hardware_chromeos.h" #include <memory> diff --git a/image_properties.h b/cros/image_properties.h index 49fe82fc..12975473 100644 --- a/image_properties.h +++ b/cros/image_properties.h @@ -18,28 +18,22 @@ // properties are meant to be constant during the life of this daemon, but can // be modified in dev-move or non-official builds. -#ifndef UPDATE_ENGINE_IMAGE_PROPERTIES_H_ -#define UPDATE_ENGINE_IMAGE_PROPERTIES_H_ +#ifndef UPDATE_ENGINE_CROS_IMAGE_PROPERTIES_H_ +#define UPDATE_ENGINE_CROS_IMAGE_PROPERTIES_H_ #include <string> namespace chromeos_update_engine { -class SystemState; - // The read-only system properties of the running image. struct ImageProperties { // The product id of the image used for all channels, except canary. std::string product_id; // The canary-channel product id. std::string canary_product_id; - // The system id for the Android Things SoM, empty for Chrome OS. - std::string system_id; // The product version of this image. std::string version; - // The system version of this image. - std::string system_version; // The version of all product components in key values pairs. std::string product_components; @@ -81,16 +75,15 @@ struct MutableImageProperties { // Loads all the image properties from the running system. In case of error // loading any of these properties from the read-only system image a default // value may be returned instead. -ImageProperties LoadImageProperties(SystemState* system_state); +ImageProperties LoadImageProperties(); // Loads the mutable image properties from the stateful partition if found or // the system image otherwise. -MutableImageProperties LoadMutableImageProperties(SystemState* system_state); +MutableImageProperties LoadMutableImageProperties(); // Stores the mutable image properties in the stateful partition. Returns // whether the operation succeeded. -bool StoreMutableImageProperties(SystemState* system_state, - const MutableImageProperties& properties); +bool StoreMutableImageProperties(const MutableImageProperties& properties); // Logs the image properties. void LogImageProperties(); @@ -103,4 +96,4 @@ void SetImagePropertiesRootPrefix(const char* test_root_prefix); } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_IMAGE_PROPERTIES_H_ +#endif // UPDATE_ENGINE_CROS_IMAGE_PROPERTIES_H_ diff --git a/image_properties_chromeos.cc b/cros/image_properties_chromeos.cc index 5ab8f052..79155b5d 100644 --- a/image_properties_chromeos.cc +++ b/cros/image_properties_chromeos.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/image_properties.h" +#include "update_engine/cros/image_properties.h" #include <string> #include <vector> @@ -26,8 +26,8 @@ #include "update_engine/common/constants.h" #include "update_engine/common/hardware_interface.h" #include "update_engine/common/platform_constants.h" +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" -#include "update_engine/system_state.h" namespace { @@ -86,7 +86,7 @@ void SetImagePropertiesRootPrefix(const char* test_root_prefix) { } } // namespace test -ImageProperties LoadImageProperties(SystemState* system_state) { +ImageProperties LoadImageProperties() { ImageProperties result; brillo::KeyValueStore lsb_release; @@ -97,7 +97,7 @@ ImageProperties LoadImageProperties(SystemState* system_state) { // In dev-mode and unofficial build we can override the image properties set // in the system image with the ones from the stateful partition, except the // channel of the current image. - HardwareInterface* const hardware = system_state->hardware(); + HardwareInterface* const hardware = SystemState::Get()->hardware(); if (!hardware->IsOfficialBuild() || !hardware->IsNormalBootMode()) LoadLsbRelease(LsbReleaseSource::kStateful, &lsb_release); @@ -124,7 +124,7 @@ ImageProperties LoadImageProperties(SystemState* system_state) { return result; } -MutableImageProperties LoadMutableImageProperties(SystemState* system_state) { +MutableImageProperties LoadMutableImageProperties() { MutableImageProperties result; brillo::KeyValueStore lsb_release; LoadLsbRelease(LsbReleaseSource::kSystem, &lsb_release); @@ -137,8 +137,7 @@ MutableImageProperties LoadMutableImageProperties(SystemState* system_state) { return result; } -bool StoreMutableImageProperties(SystemState* system_state, - const MutableImageProperties& properties) { +bool StoreMutableImageProperties(const MutableImageProperties& properties) { brillo::KeyValueStore lsb_release; LoadLsbRelease(LsbReleaseSource::kStateful, &lsb_release); lsb_release.SetString(kLsbReleaseUpdateChannelKey, properties.target_channel); diff --git a/image_properties_chromeos_unittest.cc b/cros/image_properties_chromeos_unittest.cc index d9ed6883..497554e2 100644 --- a/image_properties_chromeos_unittest.cc +++ b/cros/image_properties_chromeos_unittest.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/image_properties.h" +#include "update_engine/cros/image_properties.h" #include <string> @@ -24,7 +24,7 @@ #include "update_engine/common/constants.h" #include "update_engine/common/test_utils.h" -#include "update_engine/fake_system_state.h" +#include "update_engine/cros/fake_system_state.h" using chromeos_update_engine::test_utils::WriteFileString; using std::string; @@ -40,16 +40,15 @@ class ImagePropertiesTest : public ::testing::Test { EXPECT_TRUE(base::CreateDirectory(base::FilePath( tempdir_.GetPath().value() + kStatefulPartition + "/etc"))); test::SetImagePropertiesRootPrefix(tempdir_.GetPath().value().c_str()); + FakeSystemState::CreateInstance(); SetLockDown(false); } void SetLockDown(bool locked_down) { - fake_system_state_.fake_hardware()->SetIsOfficialBuild(locked_down); - fake_system_state_.fake_hardware()->SetIsNormalBootMode(locked_down); + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(locked_down); + FakeSystemState::Get()->fake_hardware()->SetIsNormalBootMode(locked_down); } - FakeSystemState fake_system_state_; - base::ScopedTempDir tempdir_; }; @@ -61,7 +60,7 @@ TEST_F(ImagePropertiesTest, SimpleTest) { "CHROMEOS_RELEASE_VERSION=0.2.2.3\n" "CHROMEOS_RELEASE_TRACK=dev-channel\n" "CHROMEOS_AUSERVER=http://www.google.com")); - ImageProperties props = LoadImageProperties(&fake_system_state_); + ImageProperties props = LoadImageProperties(); EXPECT_EQ("arm-generic", props.board); EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", props.product_id); EXPECT_EQ("0.2.2.3", props.version); @@ -73,7 +72,7 @@ TEST_F(ImagePropertiesTest, AppIDTest) { ASSERT_TRUE(WriteFileString( tempdir_.GetPath().Append("etc/lsb-release").value(), "CHROMEOS_RELEASE_APPID={58c35cef-9d30-476e-9098-ce20377d535d}")); - ImageProperties props = LoadImageProperties(&fake_system_state_); + ImageProperties props = LoadImageProperties(); EXPECT_EQ("{58c35cef-9d30-476e-9098-ce20377d535d}", props.product_id); } @@ -82,12 +81,12 @@ TEST_F(ImagePropertiesTest, ConfusingReleaseTest) { WriteFileString(tempdir_.GetPath().Append("etc/lsb-release").value(), "CHROMEOS_RELEASE_FOO=CHROMEOS_RELEASE_VERSION=1.2.3.4\n" "CHROMEOS_RELEASE_VERSION=0.2.2.3")); - ImageProperties props = LoadImageProperties(&fake_system_state_); + ImageProperties props = LoadImageProperties(); EXPECT_EQ("0.2.2.3", props.version); } TEST_F(ImagePropertiesTest, MissingVersionTest) { - ImageProperties props = LoadImageProperties(&fake_system_state_); + ImageProperties props = LoadImageProperties(); EXPECT_EQ("", props.version); } @@ -103,12 +102,11 @@ TEST_F(ImagePropertiesTest, OverrideTest) { "CHROMEOS_RELEASE_BOARD=x86-generic\n" "CHROMEOS_RELEASE_TRACK=beta-channel\n" "CHROMEOS_AUSERVER=https://www.google.com")); - ImageProperties props = LoadImageProperties(&fake_system_state_); + ImageProperties props = LoadImageProperties(); EXPECT_EQ("x86-generic", props.board); EXPECT_EQ("dev-channel", props.current_channel); EXPECT_EQ("https://www.google.com", props.omaha_url); - MutableImageProperties mutable_props = - LoadMutableImageProperties(&fake_system_state_); + MutableImageProperties mutable_props = LoadMutableImageProperties(); EXPECT_EQ("beta-channel", mutable_props.target_channel); } @@ -125,12 +123,11 @@ TEST_F(ImagePropertiesTest, OverrideLockDownTest) { "CHROMEOS_RELEASE_TRACK=stable-channel\n" "CHROMEOS_AUSERVER=http://www.google.com")); SetLockDown(true); - ImageProperties props = LoadImageProperties(&fake_system_state_); + ImageProperties props = LoadImageProperties(); EXPECT_EQ("arm-generic", props.board); EXPECT_EQ("dev-channel", props.current_channel); EXPECT_EQ("https://www.google.com", props.omaha_url); - MutableImageProperties mutable_props = - LoadMutableImageProperties(&fake_system_state_); + MutableImageProperties mutable_props = LoadMutableImageProperties(); EXPECT_EQ("stable-channel", mutable_props.target_channel); } @@ -141,7 +138,7 @@ TEST_F(ImagePropertiesTest, BoardAppIdUsedForNonCanaryChannelTest) { "CHROMEOS_BOARD_APPID=b\n" "CHROMEOS_CANARY_APPID=c\n" "CHROMEOS_RELEASE_TRACK=stable-channel\n")); - ImageProperties props = LoadImageProperties(&fake_system_state_); + ImageProperties props = LoadImageProperties(); EXPECT_EQ("stable-channel", props.current_channel); EXPECT_EQ("b", props.product_id); } @@ -153,7 +150,7 @@ TEST_F(ImagePropertiesTest, CanaryAppIdUsedForCanaryChannelTest) { "CHROMEOS_BOARD_APPID=b\n" "CHROMEOS_CANARY_APPID=c\n" "CHROMEOS_RELEASE_TRACK=canary-channel\n")); - ImageProperties props = LoadImageProperties(&fake_system_state_); + ImageProperties props = LoadImageProperties(); EXPECT_EQ("canary-channel", props.current_channel); EXPECT_EQ("c", props.canary_product_id); } @@ -164,7 +161,7 @@ TEST_F(ImagePropertiesTest, ReleaseAppIdUsedAsDefaultTest) { "CHROMEOS_RELEASE_APPID=r\n" "CHROMEOS_CANARY_APPID=c\n" "CHROMEOS_RELEASE_TRACK=stable-channel\n")); - ImageProperties props = LoadImageProperties(&fake_system_state_); + ImageProperties props = LoadImageProperties(); EXPECT_EQ("stable-channel", props.current_channel); EXPECT_EQ("r", props.product_id); } diff --git a/logging.cc b/cros/logging.cc index 6320e368..8b6c556b 100644 --- a/logging.cc +++ b/cros/logging.cc @@ -25,8 +25,8 @@ #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> +#include "update_engine/common/logging.h" #include "update_engine/common/utils.h" -#include "update_engine/logging.h" using std::string; @@ -46,7 +46,7 @@ void SetupLogSymlink(const string& symlink_path, const string& log_path) { base::ReplaceFile( base::FilePath(symlink_path), base::FilePath(log_path), nullptr); } - base::DeleteFile(base::FilePath(symlink_path), true); + base::DeletePathRecursively(base::FilePath(symlink_path)); if (symlink(log_path.c_str(), symlink_path.c_str()) == -1) { PLOG(ERROR) << "Unable to create symlink " << symlink_path << " pointing at " << log_path; @@ -79,7 +79,11 @@ void SetupLogging(bool log_to_system, bool log_to_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); } diff --git a/metrics_reporter_omaha.cc b/cros/metrics_reporter_omaha.cc index 14819d88..69cdb19b 100644 --- a/metrics_reporter_omaha.cc +++ b/cros/metrics_reporter_omaha.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/metrics_reporter_omaha.h" +#include "update_engine/cros/metrics_reporter_omaha.h" #include <memory> @@ -22,18 +22,18 @@ #include <base/strings/string_number_conversions.h> #include <metrics/metrics_library.h> -#include "update_engine/common/clock_interface.h" #include "update_engine/common/constants.h" #include "update_engine/common/prefs_interface.h" +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" +#include "update_engine/cros/omaha_request_params.h" #include "update_engine/metrics_utils.h" -#include "update_engine/omaha_request_params.h" -#include "update_engine/system_state.h" +using base::Time; +using base::TimeDelta; using std::string; namespace chromeos_update_engine { - namespace metrics { // UpdateEngine.Daily.* metrics. @@ -135,7 +135,8 @@ const char kMetricInstallDateProvisioningSource[] = "UpdateEngine.InstallDateProvisioningSource"; const char kMetricTimeToRebootMinutes[] = "UpdateEngine.TimeToRebootMinutes"; -std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter() { +std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter( + DynamicPartitionControlInterface* dynamic_partition_control) { return std::make_unique<MetricsReporterOmaha>(); } @@ -144,14 +145,8 @@ std::unique_ptr<MetricsReporterInterface> CreateMetricsReporter() { MetricsReporterOmaha::MetricsReporterOmaha() : metrics_lib_(new MetricsLibrary()) {} -void MetricsReporterOmaha::Initialize() { - metrics_lib_->Init(); -} - void MetricsReporterOmaha::ReportDailyMetrics(base::TimeDelta os_age) { string metric = metrics::kMetricDailyOSAgeDays; - LOG(INFO) << "Uploading " << utils::FormatTimeDelta(os_age) << " for metric " - << metric; metrics_lib_->SendToUMA(metric, static_cast<int>(os_age.InDays()), 0, // min: 0 days @@ -160,7 +155,6 @@ void MetricsReporterOmaha::ReportDailyMetrics(base::TimeDelta os_age) { } void MetricsReporterOmaha::ReportUpdateCheckMetrics( - SystemState* system_state, metrics::CheckResult result, metrics::CheckReaction reaction, metrics::DownloadErrorCode download_error_code) { @@ -172,31 +166,24 @@ void MetricsReporterOmaha::ReportUpdateCheckMetrics( metric = metrics::kMetricCheckResult; value = static_cast<int>(result); max_value = static_cast<int>(metrics::CheckResult::kNumConstants) - 1; - LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)"; metrics_lib_->SendEnumToUMA(metric, value, max_value); } if (reaction != metrics::CheckReaction::kUnset) { metric = metrics::kMetricCheckReaction; value = static_cast<int>(reaction); max_value = static_cast<int>(metrics::CheckReaction::kNumConstants) - 1; - LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)"; metrics_lib_->SendEnumToUMA(metric, value, max_value); } if (download_error_code != metrics::DownloadErrorCode::kUnset) { metric = metrics::kMetricCheckDownloadErrorCode; value = static_cast<int>(download_error_code); - LOG(INFO) << "Sending " << value << " for metric " << metric << " (sparse)"; metrics_lib_->SendSparseToUMA(metric, value); } base::TimeDelta time_since_last; - if (metrics_utils::WallclockDurationHelper( - system_state, - kPrefsMetricsCheckLastReportingTime, - &time_since_last)) { + if (WallclockDurationHelper(kPrefsMetricsCheckLastReportingTime, + &time_since_last)) { metric = metrics::kMetricCheckTimeSinceLastCheckMinutes; - LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last) - << " for metric " << metric; metrics_lib_->SendToUMA(metric, time_since_last.InMinutes(), 0, // min: 0 min @@ -206,11 +193,8 @@ void MetricsReporterOmaha::ReportUpdateCheckMetrics( base::TimeDelta uptime_since_last; static int64_t uptime_since_last_storage = 0; - if (metrics_utils::MonotonicDurationHelper( - system_state, &uptime_since_last_storage, &uptime_since_last)) { + if (MonotonicDurationHelper(&uptime_since_last_storage, &uptime_since_last)) { metric = metrics::kMetricCheckTimeSinceLastCheckUptimeMinutes; - LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last) - << " for metric " << metric; metrics_lib_->SendToUMA(metric, uptime_since_last.InMinutes(), 0, // min: 0 min @@ -219,19 +203,15 @@ void MetricsReporterOmaha::ReportUpdateCheckMetrics( } // First section of target version specified for the update. - if (system_state && system_state->request_params()) { + if (SystemState::Get()->request_params()) { string target_version = - system_state->request_params()->target_version_prefix(); + SystemState::Get()->request_params()->target_version_prefix(); value = utils::VersionPrefix(target_version); if (value != 0) { metric = metrics::kMetricCheckTargetVersion; - LOG(INFO) << "Sending " << value << " for metric " << metric - << " (sparse)"; metrics_lib_->SendSparseToUMA(metric, value); - if (system_state->request_params()->rollback_allowed()) { + if (SystemState::Get()->request_params()->rollback_allowed()) { metric = metrics::kMetricCheckRollbackTargetVersion; - LOG(INFO) << "Sending " << value << " for metric " << metric - << " (sparse)"; metrics_lib_->SendSparseToUMA(metric, value); } } @@ -243,8 +223,6 @@ void MetricsReporterOmaha::ReportAbnormallyTerminatedUpdateAttemptMetrics() { metrics::AttemptResult attempt_result = metrics::AttemptResult::kAbnormalTermination; - LOG(INFO) << "Uploading " << static_cast<int>(attempt_result) - << " for metric " << metric; metrics_lib_->SendEnumToUMA( metric, static_cast<int>(attempt_result), @@ -252,7 +230,6 @@ void MetricsReporterOmaha::ReportAbnormallyTerminatedUpdateAttemptMetrics() { } void MetricsReporterOmaha::ReportUpdateAttemptMetrics( - SystemState* system_state, int attempt_number, PayloadType payload_type, base::TimeDelta duration, @@ -261,7 +238,6 @@ void MetricsReporterOmaha::ReportUpdateAttemptMetrics( metrics::AttemptResult attempt_result, ErrorCode internal_error_code) { string metric = metrics::kMetricAttemptNumber; - LOG(INFO) << "Uploading " << attempt_number << " for metric " << metric; metrics_lib_->SendToUMA(metric, attempt_number, 0, // min: 0 attempts @@ -269,13 +245,9 @@ void MetricsReporterOmaha::ReportUpdateAttemptMetrics( 50); // num_buckets metric = metrics::kMetricAttemptPayloadType; - LOG(INFO) << "Uploading " << utils::ToString(payload_type) << " for metric " - << metric; metrics_lib_->SendEnumToUMA(metric, payload_type, kNumPayloadTypes); metric = metrics::kMetricAttemptDurationMinutes; - LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration) - << " for metric " << metric; metrics_lib_->SendToUMA(metric, duration.InMinutes(), 0, // min: 0 min @@ -283,8 +255,6 @@ void MetricsReporterOmaha::ReportUpdateAttemptMetrics( 50); // num_buckets metric = metrics::kMetricAttemptDurationUptimeMinutes; - LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration_uptime) - << " for metric " << metric; metrics_lib_->SendToUMA(metric, duration_uptime.InMinutes(), 0, // min: 0 min @@ -293,7 +263,6 @@ void MetricsReporterOmaha::ReportUpdateAttemptMetrics( metric = metrics::kMetricAttemptPayloadSizeMiB; int64_t payload_size_mib = payload_size / kNumBytesInOneMiB; - LOG(INFO) << "Uploading " << payload_size_mib << " for metric " << metric; metrics_lib_->SendToUMA(metric, payload_size_mib, 0, // min: 0 MiB @@ -301,8 +270,6 @@ void MetricsReporterOmaha::ReportUpdateAttemptMetrics( 50); // num_buckets metric = metrics::kMetricAttemptResult; - LOG(INFO) << "Uploading " << static_cast<int>(attempt_result) - << " for metric " << metric; metrics_lib_->SendEnumToUMA( metric, static_cast<int>(attempt_result), @@ -313,13 +280,9 @@ void MetricsReporterOmaha::ReportUpdateAttemptMetrics( } base::TimeDelta time_since_last; - if (metrics_utils::WallclockDurationHelper( - system_state, - kPrefsMetricsAttemptLastReportingTime, - &time_since_last)) { + if (WallclockDurationHelper(kPrefsMetricsAttemptLastReportingTime, + &time_since_last)) { metric = metrics::kMetricAttemptTimeSinceLastAttemptMinutes; - LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last) - << " for metric " << metric; metrics_lib_->SendToUMA(metric, time_since_last.InMinutes(), 0, // min: 0 min @@ -329,11 +292,8 @@ void MetricsReporterOmaha::ReportUpdateAttemptMetrics( static int64_t uptime_since_last_storage = 0; base::TimeDelta uptime_since_last; - if (metrics_utils::MonotonicDurationHelper( - system_state, &uptime_since_last_storage, &uptime_since_last)) { + if (MonotonicDurationHelper(&uptime_since_last_storage, &uptime_since_last)) { metric = metrics::kMetricAttemptTimeSinceLastAttemptUptimeMinutes; - LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last) - << " for metric " << metric; metrics_lib_->SendToUMA(metric, uptime_since_last.InMinutes(), 0, // min: 0 min @@ -351,8 +311,6 @@ void MetricsReporterOmaha::ReportUpdateAttemptDownloadMetrics( string metric = metrics::kMetricAttemptPayloadBytesDownloadedMiB; int64_t payload_bytes_downloaded_mib = payload_bytes_downloaded / kNumBytesInOneMiB; - LOG(INFO) << "Uploading " << payload_bytes_downloaded_mib << " for metric " - << metric; metrics_lib_->SendToUMA(metric, payload_bytes_downloaded_mib, 0, // min: 0 MiB @@ -361,8 +319,6 @@ void MetricsReporterOmaha::ReportUpdateAttemptDownloadMetrics( metric = metrics::kMetricAttemptPayloadDownloadSpeedKBps; int64_t payload_download_speed_kbps = payload_download_speed_bps / 1000; - LOG(INFO) << "Uploading " << payload_download_speed_kbps << " for metric " - << metric; metrics_lib_->SendToUMA(metric, payload_download_speed_kbps, 0, // min: 0 kB/s @@ -370,20 +326,15 @@ void MetricsReporterOmaha::ReportUpdateAttemptDownloadMetrics( 50); // num_buckets metric = metrics::kMetricAttemptDownloadSource; - LOG(INFO) << "Uploading " << download_source << " for metric " << metric; metrics_lib_->SendEnumToUMA(metric, download_source, kNumDownloadSources); if (payload_download_error_code != metrics::DownloadErrorCode::kUnset) { metric = metrics::kMetricAttemptDownloadErrorCode; - LOG(INFO) << "Uploading " << static_cast<int>(payload_download_error_code) - << " for metric " << metric << " (sparse)"; metrics_lib_->SendSparseToUMA( metric, static_cast<int>(payload_download_error_code)); } metric = metrics::kMetricAttemptConnectionType; - LOG(INFO) << "Uploading " << static_cast<int>(connection_type) - << " for metric " << metric; metrics_lib_->SendEnumToUMA( metric, static_cast<int>(connection_type), @@ -403,7 +354,6 @@ void MetricsReporterOmaha::ReportSuccessfulUpdateMetrics( int url_switch_count) { string metric = metrics::kMetricSuccessfulUpdatePayloadSizeMiB; int64_t mbs = payload_size / kNumBytesInOneMiB; - LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric; metrics_lib_->SendToUMA(metric, mbs, 0, // min: 0 MiB @@ -433,7 +383,6 @@ void MetricsReporterOmaha::ReportSuccessfulUpdateMetrics( } if (mbs > 0) { - LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric; metrics_lib_->SendToUMA(metric, mbs, 0, // min: 0 MiB @@ -443,8 +392,6 @@ void MetricsReporterOmaha::ReportSuccessfulUpdateMetrics( } metric = metrics::kMetricSuccessfulUpdateDownloadSourcesUsed; - LOG(INFO) << "Uploading 0x" << std::hex << download_sources_used - << " (bit flags) for metric " << metric; metrics_lib_->SendToUMA(metric, download_sources_used, 0, // min @@ -452,8 +399,6 @@ void MetricsReporterOmaha::ReportSuccessfulUpdateMetrics( 1 << kNumDownloadSources); // num_buckets metric = metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage; - LOG(INFO) << "Uploading " << download_overhead_percentage << "% for metric " - << metric; metrics_lib_->SendToUMA(metric, download_overhead_percentage, 0, // min: 0% overhead @@ -461,8 +406,6 @@ void MetricsReporterOmaha::ReportSuccessfulUpdateMetrics( 50); // num_buckets metric = metrics::kMetricSuccessfulUpdateUrlSwitchCount; - LOG(INFO) << "Uploading " << url_switch_count << " (count) for metric " - << metric; metrics_lib_->SendToUMA(metric, url_switch_count, 0, // min: 0 URL switches @@ -470,8 +413,6 @@ void MetricsReporterOmaha::ReportSuccessfulUpdateMetrics( 50); // num_buckets metric = metrics::kMetricSuccessfulUpdateTotalDurationMinutes; - LOG(INFO) << "Uploading " << utils::FormatTimeDelta(total_duration) - << " for metric " << metric; metrics_lib_->SendToUMA(metric, static_cast<int>(total_duration.InMinutes()), 0, // min: 0 min @@ -479,8 +420,6 @@ void MetricsReporterOmaha::ReportSuccessfulUpdateMetrics( 50); // num_buckets metric = metrics::kMetricSuccessfulUpdateTotalDurationUptimeMinutes; - LOG(INFO) << "Uploading " << utils::FormatTimeDelta(total_duration_uptime) - << " for metric " << metric; metrics_lib_->SendToUMA(metric, static_cast<int>(total_duration_uptime.InMinutes()), 0, // min: 0 min @@ -488,8 +427,6 @@ void MetricsReporterOmaha::ReportSuccessfulUpdateMetrics( 50); // num_buckets metric = metrics::kMetricSuccessfulUpdateRebootCount; - LOG(INFO) << "Uploading reboot count of " << reboot_count << " for metric " - << metric; metrics_lib_->SendToUMA(metric, reboot_count, 0, // min: 0 reboots @@ -498,8 +435,6 @@ void MetricsReporterOmaha::ReportSuccessfulUpdateMetrics( metric = metrics::kMetricSuccessfulUpdatePayloadType; metrics_lib_->SendEnumToUMA(metric, payload_type, kNumPayloadTypes); - LOG(INFO) << "Uploading " << utils::ToString(payload_type) << " for metric " - << metric; metric = metrics::kMetricSuccessfulUpdateAttemptCount; metrics_lib_->SendToUMA(metric, @@ -507,11 +442,8 @@ void MetricsReporterOmaha::ReportSuccessfulUpdateMetrics( 1, // min: 1 attempt 50, // max: 50 attempts 50); // num_buckets - LOG(INFO) << "Uploading " << attempt_count << " for metric " << metric; metric = metrics::kMetricSuccessfulUpdateUpdatesAbandonedCount; - LOG(INFO) << "Uploading " << updates_abandoned_count << " (count) for metric " - << metric; metrics_lib_->SendToUMA(metric, updates_abandoned_count, 0, // min: 0 counts @@ -523,7 +455,6 @@ void MetricsReporterOmaha::ReportRollbackMetrics( metrics::RollbackResult result) { string metric = metrics::kMetricRollbackResult; int value = static_cast<int>(result); - LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)"; metrics_lib_->SendEnumToUMA( metric, value, static_cast<int>(metrics::RollbackResult::kNumConstants)); } @@ -534,7 +465,6 @@ void MetricsReporterOmaha::ReportEnterpriseRollbackMetrics( string metric = metrics::kMetricEnterpriseRollbackSuccess; if (!success) metric = metrics::kMetricEnterpriseRollbackFailure; - LOG(INFO) << "Sending " << value << " for metric " << metric; metrics_lib_->SendSparseToUMA(metric, value); } @@ -551,8 +481,6 @@ void MetricsReporterOmaha::ReportCertificateCheckMetrics( case ServerToCheck::kNone: return; } - LOG(INFO) << "Uploading " << static_cast<int>(result) << " for metric " - << metric; metrics_lib_->SendEnumToUMA( metric, static_cast<int>(result), @@ -566,9 +494,6 @@ void MetricsReporterOmaha::ReportFailedUpdateCount(int target_attempt) { 1, // min value 50, // max value kNumDefaultUmaBuckets); - - LOG(INFO) << "Uploading " << target_attempt << " (count) for metric " - << metric; } void MetricsReporterOmaha::ReportTimeToReboot(int time_to_reboot_minutes) { @@ -578,9 +503,6 @@ void MetricsReporterOmaha::ReportTimeToReboot(int time_to_reboot_minutes) { 0, // min: 0 minute 30 * 24 * 60, // max: 1 month (approx) kNumDefaultUmaBuckets); - - LOG(INFO) << "Uploading " << time_to_reboot_minutes << " for metric " - << metric; } void MetricsReporterOmaha::ReportInstallDateProvisioningSource(int source, @@ -592,7 +514,6 @@ void MetricsReporterOmaha::ReportInstallDateProvisioningSource(int source, void MetricsReporterOmaha::ReportInternalErrorCode(ErrorCode error_code) { auto metric = metrics::kMetricAttemptInternalErrorCode; - LOG(INFO) << "Uploading " << error_code << " for metric " << metric; metrics_lib_->SendEnumToUMA(metric, static_cast<int>(error_code), static_cast<int>(ErrorCode::kUmaReportedMax)); @@ -604,18 +525,14 @@ void MetricsReporterOmaha::ReportKeyVersionMetrics( bool kernel_max_rollforward_success) { int value = kernel_min_version; string metric = metrics::kMetricKernelMinVersion; - LOG(INFO) << "Sending " << value << " for metric " << metric; metrics_lib_->SendSparseToUMA(metric, value); value = kernel_max_rollforward_version; metric = metrics::kMetricKernelMaxRollforwardVersion; - LOG(INFO) << "Sending " << value << " for metric " << metric; metrics_lib_->SendSparseToUMA(metric, value); bool bool_value = kernel_max_rollforward_success; metric = metrics::kMetricKernelMaxRollforwardSetSuccess; - LOG(INFO) << "Sending " << bool_value << " for metric " << metric - << " (bool)"; metrics_lib_->SendBoolToUMA(metric, bool_value); } @@ -625,7 +542,6 @@ void MetricsReporterOmaha::ReportEnterpriseUpdateSeenToDownloadDays( has_time_restriction_policy ? metrics::kMetricSuccessfulUpdateDurationFromSeenTimeRestrictedDays : metrics::kMetricSuccessfulUpdateDurationFromSeenDays; - LOG(INFO) << "Sending " << time_to_update_days << " for metric " << metric; metrics_lib_->SendToUMA(metric, time_to_update_days, @@ -634,4 +550,44 @@ void MetricsReporterOmaha::ReportEnterpriseUpdateSeenToDownloadDays( 50); // num_buckets } +bool MetricsReporterOmaha::WallclockDurationHelper( + const std::string& state_variable_key, + TimeDelta* out_duration) { + bool ret = false; + Time now = SystemState::Get()->clock()->GetWallclockTime(); + int64_t stored_value; + if (SystemState::Get()->prefs()->GetInt64(state_variable_key, + &stored_value)) { + Time stored_time = Time::FromInternalValue(stored_value); + if (stored_time > now) { + LOG(ERROR) << "Stored time-stamp used for " << state_variable_key + << " is in the future."; + } else { + *out_duration = now - stored_time; + ret = true; + } + } + + if (!SystemState::Get()->prefs()->SetInt64(state_variable_key, + now.ToInternalValue())) { + LOG(ERROR) << "Error storing time-stamp in " << state_variable_key; + } + + return ret; +} + +bool MetricsReporterOmaha::MonotonicDurationHelper(int64_t* storage, + TimeDelta* out_duration) { + bool ret = false; + Time now = SystemState::Get()->clock()->GetMonotonicTime(); + if (*storage != 0) { + Time stored_time = Time::FromInternalValue(*storage); + *out_duration = now - stored_time; + ret = true; + } + *storage = now.ToInternalValue(); + + return ret; +} + } // namespace chromeos_update_engine diff --git a/metrics_reporter_omaha.h b/cros/metrics_reporter_omaha.h index 5680dec0..b6ffccee 100644 --- a/metrics_reporter_omaha.h +++ b/cros/metrics_reporter_omaha.h @@ -14,21 +14,21 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_METRICS_REPORTER_OMAHA_H_ -#define UPDATE_ENGINE_METRICS_REPORTER_OMAHA_H_ +#ifndef UPDATE_ENGINE_CROS_METRICS_REPORTER_OMAHA_H_ +#define UPDATE_ENGINE_CROS_METRICS_REPORTER_OMAHA_H_ #include <memory> #include <string> #include <base/time/time.h> +#include <gtest/gtest_prod.h> // for FRIEND_TEST #include <metrics/metrics_library.h> #include "update_engine/certificate_checker.h" #include "update_engine/common/constants.h" #include "update_engine/common/error_code.h" -#include "update_engine/metrics_constants.h" -#include "update_engine/metrics_reporter_interface.h" -#include "update_engine/system_state.h" +#include "update_engine/common/metrics_constants.h" +#include "update_engine/common/metrics_reporter_interface.h" namespace chromeos_update_engine { @@ -108,8 +108,6 @@ class MetricsReporterOmaha : public MetricsReporterInterface { ~MetricsReporterOmaha() override = default; - void Initialize() override; - void ReportRollbackMetrics(metrics::RollbackResult result) override; void ReportEnterpriseRollbackMetrics( @@ -118,13 +116,11 @@ class MetricsReporterOmaha : public MetricsReporterInterface { void ReportDailyMetrics(base::TimeDelta os_age) override; void ReportUpdateCheckMetrics( - SystemState* system_state, metrics::CheckResult result, metrics::CheckReaction reaction, metrics::DownloadErrorCode download_error_code) override; - void ReportUpdateAttemptMetrics(SystemState* system_state, - int attempt_number, + void ReportUpdateAttemptMetrics(int attempt_number, PayloadType payload_type, base::TimeDelta duration, base::TimeDelta duration_uptime, @@ -173,6 +169,28 @@ class MetricsReporterOmaha : public MetricsReporterInterface { private: friend class MetricsReporterOmahaTest; + FRIEND_TEST(MetricsReporterOmahaTest, WallclockDurationHelper); + FRIEND_TEST(MetricsReporterOmahaTest, MonotonicDurationHelper); + + // This function returns the duration on the wallclock since the last + // time it was called for the same |state_variable_key| value. + // + // If the function returns |true|, the duration (always non-negative) + // is returned in |out_duration|. If the function returns |false| + // something went wrong or there was no previous measurement. + bool WallclockDurationHelper(const std::string& state_variable_key, + base::TimeDelta* out_duration); + + // This function returns the duration on the monotonic clock since the + // last time it was called for the same |storage| pointer. + // + // You should pass a pointer to a 64-bit integer in |storage| which + // should be initialized to 0. + // + // If the function returns |true|, the duration (always non-negative) + // is returned in |out_duration|. If the function returns |false| + // something went wrong or there was no previous measurement. + bool MonotonicDurationHelper(int64_t* storage, base::TimeDelta* out_duration); std::unique_ptr<MetricsLibraryInterface> metrics_lib_; @@ -181,4 +199,4 @@ class MetricsReporterOmaha : public MetricsReporterInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_METRICS_REPORTER_OMAHA_H_ +#endif // UPDATE_ENGINE_CROS_METRICS_REPORTER_OMAHA_H_ diff --git a/metrics_reporter_omaha_unittest.cc b/cros/metrics_reporter_omaha_unittest.cc index 545d02f4..cdc44cdc 100644 --- a/metrics_reporter_omaha_unittest.cc +++ b/cros/metrics_reporter_omaha_unittest.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/metrics_reporter_omaha.h" +#include "update_engine/cros/metrics_reporter_omaha.h" #include <memory> #include <string> @@ -25,8 +25,7 @@ #include <metrics/metrics_library_mock.h> #include "update_engine/common/fake_clock.h" -#include "update_engine/common/fake_prefs.h" -#include "update_engine/fake_system_state.h" +#include "update_engine/cros/fake_system_state.h" using base::TimeDelta; using testing::_; @@ -40,12 +39,16 @@ class MetricsReporterOmahaTest : public ::testing::Test { // Reset the metrics_lib_ to a mock library. void SetUp() override { + FakeSystemState::CreateInstance(); + fake_clock_ = FakeSystemState::Get()->fake_clock(); mock_metrics_lib_ = new testing::NiceMock<MetricsLibraryMock>(); reporter_.metrics_lib_.reset(mock_metrics_lib_); } testing::NiceMock<MetricsLibraryMock>* mock_metrics_lib_; MetricsReporterOmaha reporter_; + + FakeClock* fake_clock_; }; TEST_F(MetricsReporterOmahaTest, ReportDailyMetrics) { @@ -58,15 +61,9 @@ TEST_F(MetricsReporterOmahaTest, ReportDailyMetrics) { } TEST_F(MetricsReporterOmahaTest, ReportUpdateCheckMetrics) { - FakeSystemState fake_system_state; - FakeClock fake_clock; - FakePrefs fake_prefs; - // We need to execute the report twice to test the time since last report. - fake_system_state.set_clock(&fake_clock); - fake_system_state.set_prefs(&fake_prefs); - fake_clock.SetWallclockTime(base::Time::FromInternalValue(1000000)); - fake_clock.SetMonotonicTime(base::Time::FromInternalValue(1000000)); + fake_clock_->SetWallclockTime(base::Time::FromInternalValue(1000000)); + fake_clock_->SetMonotonicTime(base::Time::FromInternalValue(1000000)); metrics::CheckResult result = metrics::CheckResult::kUpdateAvailable; metrics::CheckReaction reaction = metrics::CheckReaction::kIgnored; @@ -104,24 +101,20 @@ TEST_F(MetricsReporterOmahaTest, ReportUpdateCheckMetrics) { metrics::kMetricCheckTimeSinceLastCheckUptimeMinutes, 1, _, _, _)) .Times(1); - reporter_.ReportUpdateCheckMetrics( - &fake_system_state, result, reaction, error_code); + reporter_.ReportUpdateCheckMetrics(result, reaction, error_code); // Advance the clock by 1 minute and report the same metrics again. - fake_clock.SetWallclockTime(base::Time::FromInternalValue(61000000)); - fake_clock.SetMonotonicTime(base::Time::FromInternalValue(61000000)); + fake_clock_->SetWallclockTime(base::Time::FromInternalValue(61000000)); + fake_clock_->SetMonotonicTime(base::Time::FromInternalValue(61000000)); // Allow rollback - reporter_.ReportUpdateCheckMetrics( - &fake_system_state, result, reaction, error_code); + reporter_.ReportUpdateCheckMetrics(result, reaction, error_code); } TEST_F(MetricsReporterOmahaTest, ReportUpdateCheckMetricsPinned) { - FakeSystemState fake_system_state; - - OmahaRequestParams params(&fake_system_state); + OmahaRequestParams params; params.set_target_version_prefix("10575."); params.set_rollback_allowed(false); - fake_system_state.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); metrics::CheckResult result = metrics::CheckResult::kUpdateAvailable; metrics::CheckReaction reaction = metrics::CheckReaction::kIgnored; @@ -138,17 +131,14 @@ TEST_F(MetricsReporterOmahaTest, ReportUpdateCheckMetricsPinned) { SendSparseToUMA(metrics::kMetricCheckRollbackTargetVersion, _)) .Times(0); - reporter_.ReportUpdateCheckMetrics( - &fake_system_state, result, reaction, error_code); + reporter_.ReportUpdateCheckMetrics(result, reaction, error_code); } TEST_F(MetricsReporterOmahaTest, ReportUpdateCheckMetricsRollback) { - FakeSystemState fake_system_state; - - OmahaRequestParams params(&fake_system_state); + OmahaRequestParams params; params.set_target_version_prefix("10575."); params.set_rollback_allowed(true); - fake_system_state.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); metrics::CheckResult result = metrics::CheckResult::kUpdateAvailable; metrics::CheckReaction reaction = metrics::CheckReaction::kIgnored; @@ -166,8 +156,7 @@ TEST_F(MetricsReporterOmahaTest, ReportUpdateCheckMetricsRollback) { SendSparseToUMA(metrics::kMetricCheckRollbackTargetVersion, 10575)) .Times(1); - reporter_.ReportUpdateCheckMetrics( - &fake_system_state, result, reaction, error_code); + reporter_.ReportUpdateCheckMetrics(result, reaction, error_code); } TEST_F(MetricsReporterOmahaTest, @@ -183,14 +172,8 @@ TEST_F(MetricsReporterOmahaTest, } TEST_F(MetricsReporterOmahaTest, ReportUpdateAttemptMetrics) { - FakeSystemState fake_system_state; - FakeClock fake_clock; - FakePrefs fake_prefs; - - fake_system_state.set_clock(&fake_clock); - fake_system_state.set_prefs(&fake_prefs); - fake_clock.SetWallclockTime(base::Time::FromInternalValue(1000000)); - fake_clock.SetMonotonicTime(base::Time::FromInternalValue(1000000)); + fake_clock_->SetWallclockTime(base::Time::FromInternalValue(1000000)); + fake_clock_->SetMonotonicTime(base::Time::FromInternalValue(1000000)); int attempt_number = 1; PayloadType payload_type = kPayloadTypeFull; @@ -252,8 +235,7 @@ TEST_F(MetricsReporterOmahaTest, ReportUpdateAttemptMetrics) { metrics::kMetricAttemptTimeSinceLastAttemptUptimeMinutes, 1, _, _, _)) .Times(1); - reporter_.ReportUpdateAttemptMetrics(&fake_system_state, - attempt_number, + reporter_.ReportUpdateAttemptMetrics(attempt_number, payload_type, duration, duration_uptime, @@ -262,10 +244,9 @@ TEST_F(MetricsReporterOmahaTest, ReportUpdateAttemptMetrics) { internal_error_code); // Advance the clock by 1 minute and report the same metrics again. - fake_clock.SetWallclockTime(base::Time::FromInternalValue(61000000)); - fake_clock.SetMonotonicTime(base::Time::FromInternalValue(61000000)); - reporter_.ReportUpdateAttemptMetrics(&fake_system_state, - attempt_number, + fake_clock_->SetWallclockTime(base::Time::FromInternalValue(61000000)); + fake_clock_->SetMonotonicTime(base::Time::FromInternalValue(61000000)); + reporter_.ReportUpdateAttemptMetrics(attempt_number, payload_type, duration, duration_uptime, @@ -538,4 +519,89 @@ TEST_F(MetricsReporterOmahaTest, true /* has_time_restriction_policy */, kDaysToUpdate); } +TEST_F(MetricsReporterOmahaTest, WallclockDurationHelper) { + base::TimeDelta duration; + const std::string state_variable_key = "test-prefs"; + + // Initialize wallclock to 1 sec. + fake_clock_->SetWallclockTime(base::Time::FromInternalValue(1000000)); + + // First time called so no previous measurement available. + EXPECT_FALSE( + reporter_.WallclockDurationHelper(state_variable_key, &duration)); + + // Next time, we should get zero since the clock didn't advance. + EXPECT_TRUE(reporter_.WallclockDurationHelper(state_variable_key, &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // We can also call it as many times as we want with it being + // considered a failure. + EXPECT_TRUE(reporter_.WallclockDurationHelper(state_variable_key, &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + EXPECT_TRUE(reporter_.WallclockDurationHelper(state_variable_key, &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // Advance the clock one second, then we should get 1 sec on the + // next call and 0 sec on the subsequent call. + fake_clock_->SetWallclockTime(base::Time::FromInternalValue(2000000)); + EXPECT_TRUE(reporter_.WallclockDurationHelper(state_variable_key, &duration)); + EXPECT_EQ(duration.InSeconds(), 1); + EXPECT_TRUE(reporter_.WallclockDurationHelper(state_variable_key, &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // Advance clock two seconds and we should get 2 sec and then 0 sec. + fake_clock_->SetWallclockTime(base::Time::FromInternalValue(4000000)); + EXPECT_TRUE(reporter_.WallclockDurationHelper(state_variable_key, &duration)); + EXPECT_EQ(duration.InSeconds(), 2); + EXPECT_TRUE(reporter_.WallclockDurationHelper(state_variable_key, &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // There's a possibility that the wallclock can go backwards (NTP + // adjustments, for example) so check that we properly handle this + // case. + fake_clock_->SetWallclockTime(base::Time::FromInternalValue(3000000)); + EXPECT_FALSE( + reporter_.WallclockDurationHelper(state_variable_key, &duration)); + fake_clock_->SetWallclockTime(base::Time::FromInternalValue(4000000)); + EXPECT_TRUE(reporter_.WallclockDurationHelper(state_variable_key, &duration)); + EXPECT_EQ(duration.InSeconds(), 1); +} + +TEST_F(MetricsReporterOmahaTest, MonotonicDurationHelper) { + int64_t storage = 0; + base::TimeDelta duration; + + // Initialize monotonic clock to 1 sec. + fake_clock_->SetMonotonicTime(base::Time::FromInternalValue(1000000)); + + // First time called so no previous measurement available. + EXPECT_FALSE(reporter_.MonotonicDurationHelper(&storage, &duration)); + + // Next time, we should get zero since the clock didn't advance. + EXPECT_TRUE(reporter_.MonotonicDurationHelper(&storage, &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // We can also call it as many times as we want with it being + // considered a failure. + EXPECT_TRUE(reporter_.MonotonicDurationHelper(&storage, &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + EXPECT_TRUE(reporter_.MonotonicDurationHelper(&storage, &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // Advance the clock one second, then we should get 1 sec on the + // next call and 0 sec on the subsequent call. + fake_clock_->SetMonotonicTime(base::Time::FromInternalValue(2000000)); + EXPECT_TRUE(reporter_.MonotonicDurationHelper(&storage, &duration)); + EXPECT_EQ(duration.InSeconds(), 1); + EXPECT_TRUE(reporter_.MonotonicDurationHelper(&storage, &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // Advance clock two seconds and we should get 2 sec and then 0 sec. + fake_clock_->SetMonotonicTime(base::Time::FromInternalValue(4000000)); + EXPECT_TRUE(reporter_.MonotonicDurationHelper(&storage, &duration)); + EXPECT_EQ(duration.InSeconds(), 2); + EXPECT_TRUE(reporter_.MonotonicDurationHelper(&storage, &duration)); + EXPECT_EQ(duration.InSeconds(), 0); +} + } // namespace chromeos_update_engine diff --git a/mock_connection_manager.h b/cros/mock_connection_manager.h index 2fff68c6..899a49b1 100644 --- a/mock_connection_manager.h +++ b/cros/mock_connection_manager.h @@ -14,12 +14,12 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_ -#define UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_ +#ifndef UPDATE_ENGINE_CROS_MOCK_CONNECTION_MANAGER_H_ +#define UPDATE_ENGINE_CROS_MOCK_CONNECTION_MANAGER_H_ #include <gmock/gmock.h> -#include "update_engine/connection_manager_interface.h" +#include "update_engine/cros/connection_manager_interface.h" namespace chromeos_update_engine { @@ -41,4 +41,4 @@ class MockConnectionManager : public ConnectionManagerInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_ +#endif // UPDATE_ENGINE_CROS_MOCK_CONNECTION_MANAGER_H_ diff --git a/mock_omaha_request_params.h b/cros/mock_omaha_request_params.h index 41bdc195..1e218124 100644 --- a/mock_omaha_request_params.h +++ b/cros/mock_omaha_request_params.h @@ -14,21 +14,20 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_MOCK_OMAHA_REQUEST_PARAMS_H_ -#define UPDATE_ENGINE_MOCK_OMAHA_REQUEST_PARAMS_H_ +#ifndef UPDATE_ENGINE_CROS_MOCK_OMAHA_REQUEST_PARAMS_H_ +#define UPDATE_ENGINE_CROS_MOCK_OMAHA_REQUEST_PARAMS_H_ #include <string> #include <gmock/gmock.h> -#include "update_engine/omaha_request_params.h" +#include "update_engine/cros/omaha_request_params.h" namespace chromeos_update_engine { class MockOmahaRequestParams : public OmahaRequestParams { public: - explicit MockOmahaRequestParams(SystemState* system_state) - : OmahaRequestParams(system_state) { + MockOmahaRequestParams() : OmahaRequestParams() { // Delegate all calls to the parent instance by default. This helps the // migration from tests using the real RequestParams when they should have // use a fake or mock. @@ -79,4 +78,4 @@ class MockOmahaRequestParams : public OmahaRequestParams { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_MOCK_OMAHA_REQUEST_PARAMS_H_ +#endif // UPDATE_ENGINE_CROS_MOCK_OMAHA_REQUEST_PARAMS_H_ diff --git a/mock_p2p_manager.h b/cros/mock_p2p_manager.h index fd670345..273f7f9c 100644 --- a/mock_p2p_manager.h +++ b/cros/mock_p2p_manager.h @@ -14,12 +14,12 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_MOCK_P2P_MANAGER_H_ -#define UPDATE_ENGINE_MOCK_P2P_MANAGER_H_ +#ifndef UPDATE_ENGINE_CROS_MOCK_P2P_MANAGER_H_ +#define UPDATE_ENGINE_CROS_MOCK_P2P_MANAGER_H_ #include <string> -#include "update_engine/fake_p2p_manager.h" +#include "update_engine/cros/fake_p2p_manager.h" #include <gmock/gmock.h> @@ -99,4 +99,4 @@ class MockP2PManager : public P2PManager { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_MOCK_P2P_MANAGER_H_ +#endif // UPDATE_ENGINE_CROS_MOCK_P2P_MANAGER_H_ diff --git a/mock_payload_state.h b/cros/mock_payload_state.h index ad22de52..211b96d6 100644 --- a/mock_payload_state.h +++ b/cros/mock_payload_state.h @@ -14,21 +14,20 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_ -#define UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_ +#ifndef UPDATE_ENGINE_CROS_MOCK_PAYLOAD_STATE_H_ +#define UPDATE_ENGINE_CROS_MOCK_PAYLOAD_STATE_H_ #include <string> #include <gmock/gmock.h> -#include "update_engine/omaha_request_action.h" -#include "update_engine/payload_state_interface.h" +#include "update_engine/cros/payload_state_interface.h" namespace chromeos_update_engine { class MockPayloadState : public PayloadStateInterface { public: - bool Initialize(SystemState* system_state) { return true; } + bool Initialize() { return true; } // Significant methods. MOCK_METHOD1(SetResponse, void(const OmahaResponse& response)); @@ -81,4 +80,4 @@ class MockPayloadState : public PayloadStateInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_ +#endif // UPDATE_ENGINE_CROS_MOCK_PAYLOAD_STATE_H_ diff --git a/mock_power_manager.h b/cros/mock_power_manager.h index 8363171d..d4a86820 100644 --- a/mock_power_manager.h +++ b/cros/mock_power_manager.h @@ -14,12 +14,12 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_MOCK_POWER_MANAGER_H_ -#define UPDATE_ENGINE_MOCK_POWER_MANAGER_H_ +#ifndef UPDATE_ENGINE_CROS_MOCK_POWER_MANAGER_H_ +#define UPDATE_ENGINE_CROS_MOCK_POWER_MANAGER_H_ #include <gmock/gmock.h> -#include "update_engine/power_manager_interface.h" +#include "update_engine/cros/power_manager_interface.h" namespace chromeos_update_engine { @@ -32,4 +32,4 @@ class MockPowerManager : public PowerManagerInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_MOCK_POWER_MANAGER_H_ +#endif // UPDATE_ENGINE_CROS_MOCK_POWER_MANAGER_H_ diff --git a/mock_update_attempter.h b/cros/mock_update_attempter.h index 5df5a6b1..be8cfccc 100644 --- a/mock_update_attempter.h +++ b/cros/mock_update_attempter.h @@ -14,13 +14,13 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_ -#define UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_ +#ifndef UPDATE_ENGINE_CROS_MOCK_UPDATE_ATTEMPTER_H_ +#define UPDATE_ENGINE_CROS_MOCK_UPDATE_ATTEMPTER_H_ #include <string> #include <vector> -#include "update_engine/update_attempter.h" +#include "update_engine/cros/update_attempter.h" #include <gmock/gmock.h> @@ -30,14 +30,10 @@ class MockUpdateAttempter : public UpdateAttempter { public: using UpdateAttempter::UpdateAttempter; - MOCK_METHOD7(Update, - void(const std::string& app_version, - const std::string& omaha_url, - const std::string& target_channel, - const std::string& target_version_prefix, - bool rollback_allowed, - bool obey_proxies, - bool interactive)); + MOCK_METHOD(void, + Update, + (const chromeos_update_manager::UpdateCheckParams& params), + (override)); MOCK_METHOD1(GetStatus, bool(update_engine::UpdateEngineStatus* out_status)); @@ -53,9 +49,13 @@ class MockUpdateAttempter : public UpdateAttempter { UpdateAttemptFlags flags)); MOCK_METHOD2(CheckForInstall, - bool(const std::vector<std::string>& dlc_module_ids, + bool(const std::vector<std::string>& dlc_ids, const std::string& omaha_url)); + MOCK_METHOD2(SetDlcActiveValue, bool(bool, const std::string&)); + + MOCK_CONST_METHOD0(GetExcluder, ExcluderInterface*(void)); + MOCK_METHOD0(RefreshDevicePolicy, void(void)); MOCK_CONST_METHOD0(consecutive_failed_update_checks, unsigned int(void)); @@ -65,4 +65,4 @@ class MockUpdateAttempter : public UpdateAttempter { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_ +#endif // UPDATE_ENGINE_CROS_MOCK_UPDATE_ATTEMPTER_H_ diff --git a/omaha_request_action.cc b/cros/omaha_request_action.cc index fae9471c..1e5c15ff 100644 --- a/omaha_request_action.cc +++ b/cros/omaha_request_action.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/omaha_request_action.h" +#include "update_engine/cros/omaha_request_action.h" #include <inttypes.h> @@ -26,7 +26,9 @@ #include <vector> #include <base/bind.h> +#include <base/files/file_util.h> #include <base/logging.h> +#include <base/optional.h> #include <base/rand_util.h> #include <base/strings/string_number_conversions.h> #include <base/strings/string_split.h> @@ -42,21 +44,25 @@ #include "update_engine/common/constants.h" #include "update_engine/common/hardware_interface.h" #include "update_engine/common/hash_calculator.h" +#include "update_engine/common/metrics_reporter_interface.h" #include "update_engine/common/platform_constants.h" +#include "update_engine/common/prefs.h" #include "update_engine/common/prefs_interface.h" +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" -#include "update_engine/connection_manager_interface.h" -#include "update_engine/metrics_reporter_interface.h" +#include "update_engine/cros/connection_manager_interface.h" +#include "update_engine/cros/omaha_request_builder_xml.h" +#include "update_engine/cros/omaha_request_params.h" +#include "update_engine/cros/p2p_manager.h" +#include "update_engine/cros/payload_state_interface.h" +#include "update_engine/cros/update_attempter.h" #include "update_engine/metrics_utils.h" -#include "update_engine/omaha_request_params.h" -#include "update_engine/p2p_manager.h" -#include "update_engine/payload_state_interface.h" +using base::Optional; using base::Time; using base::TimeDelta; using chromeos_update_manager::kRollforwardInfinity; using std::map; -using std::numeric_limits; using std::string; using std::vector; @@ -93,6 +99,7 @@ constexpr char kAttrCohortName[] = "cohortname"; constexpr char kAttrElapsedDays[] = "elapsed_days"; constexpr char kAttrElapsedSeconds[] = "elapsed_seconds"; constexpr char kAttrEvent[] = "event"; +constexpr char kAttrFp[] = "fp"; constexpr char kAttrHashSha256[] = "hash_sha256"; // Deprecated: "hash"; Although we still need to pass it from the server for // backward compatibility. @@ -106,417 +113,85 @@ constexpr char kAttrStatus[] = "status"; constexpr char kValPostInstall[] = "postinstall"; constexpr char kValNoUpdate[] = "noupdate"; -constexpr char kOmahaUpdaterVersion[] = "0.1.0.0"; - -// X-Goog-Update headers. -constexpr char kXGoogleUpdateInteractivity[] = "X-Goog-Update-Interactivity"; -constexpr char kXGoogleUpdateAppId[] = "X-Goog-Update-AppId"; -constexpr char kXGoogleUpdateUpdater[] = "X-Goog-Update-Updater"; - -// updatecheck attributes (without the underscore prefix). -constexpr char kAttrEol[] = "eol"; -constexpr char kAttrRollback[] = "rollback"; -constexpr char kAttrFirmwareVersion[] = "firmware_version"; -constexpr char kAttrKernelVersion[] = "kernel_version"; - -namespace { - -// Returns an XML ping element attribute assignment with attribute -// |name| and value |ping_days| if |ping_days| has a value that needs -// to be sent, or an empty string otherwise. -string GetPingAttribute(const string& name, int ping_days) { - if (ping_days > 0 || ping_days == OmahaRequestAction::kNeverPinged) - return base::StringPrintf(" %s=\"%d\"", name.c_str(), ping_days); - return ""; -} - -// Returns an XML ping element if any of the elapsed days need to be -// sent, or an empty string otherwise. -string GetPingXml(int ping_active_days, int ping_roll_call_days) { - string ping_active = GetPingAttribute("a", ping_active_days); - string ping_roll_call = GetPingAttribute("r", ping_roll_call_days); - if (!ping_active.empty() || !ping_roll_call.empty()) { - return base::StringPrintf(" <ping active=\"1\"%s%s></ping>\n", - ping_active.c_str(), - ping_roll_call.c_str()); - } - return ""; -} - -// Returns an XML that goes into the body of the <app> element of the Omaha -// request based on the given parameters. -string GetAppBody(const OmahaEvent* event, - OmahaRequestParams* params, - bool ping_only, - bool include_ping, - bool skip_updatecheck, - int ping_active_days, - int ping_roll_call_days, - PrefsInterface* prefs) { - string app_body; - if (event == nullptr) { - if (include_ping) - app_body = GetPingXml(ping_active_days, ping_roll_call_days); - if (!ping_only) { - if (!skip_updatecheck) { - app_body += " <updatecheck"; - if (!params->target_version_prefix().empty()) { - app_body += base::StringPrintf( - " targetversionprefix=\"%s\"", - XmlEncodeWithDefault(params->target_version_prefix(), "") - .c_str()); - // Rollback requires target_version_prefix set. - if (params->rollback_allowed()) { - app_body += " rollback_allowed=\"true\""; - } - } - app_body += "></updatecheck>\n"; - } - - // If this is the first update check after a reboot following a previous - // update, generate an event containing the previous version number. If - // the previous version preference file doesn't exist the event is still - // generated with a previous version of 0.0.0.0 -- this is relevant for - // older clients or new installs. The previous version event is not sent - // for ping-only requests because they come before the client has - // rebooted. The previous version event is also not sent if it was already - // sent for this new version with a previous updatecheck. - string prev_version; - if (!prefs->GetString(kPrefsPreviousVersion, &prev_version)) { - prev_version = "0.0.0.0"; - } - // We only store a non-empty previous version value after a successful - // update in the previous boot. After reporting it back to the server, - // we clear the previous version value so it doesn't get reported again. - if (!prev_version.empty()) { - app_body += base::StringPrintf( - " <event eventtype=\"%d\" eventresult=\"%d\" " - "previousversion=\"%s\"></event>\n", - OmahaEvent::kTypeRebootedAfterUpdate, - OmahaEvent::kResultSuccess, - XmlEncodeWithDefault(prev_version, "0.0.0.0").c_str()); - LOG_IF(WARNING, !prefs->SetString(kPrefsPreviousVersion, "")) - << "Unable to reset the previous version."; - } - } - } else { - // The error code is an optional attribute so append it only if the result - // is not success. - string error_code; - if (event->result != OmahaEvent::kResultSuccess) { - error_code = base::StringPrintf(" errorcode=\"%d\"", - static_cast<int>(event->error_code)); - } - app_body = base::StringPrintf( - " <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n", - event->type, - event->result, - error_code.c_str()); - } - - return app_body; -} - -// Returns the cohort* argument to include in the <app> tag for the passed -// |arg_name| and |prefs_key|, if any. The return value is suitable to -// concatenate to the list of arguments and includes a space at the end. -string GetCohortArgXml(PrefsInterface* prefs, - const string arg_name, - const string prefs_key) { - // There's nothing wrong with not having a given cohort setting, so we check - // existence first to avoid the warning log message. - if (!prefs->Exists(prefs_key)) - return ""; - string cohort_value; - 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 - // to a compromised stateful partition making the update check fail in low - // network environments envent after a reboot. - if (cohort_value.size() > 1024) { - LOG(WARNING) << "The omaha cohort setting " << arg_name - << " has a too big value, which must be an error or an " - "attacker trying to inhibit updates."; - return ""; - } - - string escaped_xml_value; - if (!XmlEncode(cohort_value, &escaped_xml_value)) { - LOG(WARNING) << "The omaha cohort setting " << arg_name - << " is ASCII-7 invalid, ignoring it."; - return ""; - } - - return base::StringPrintf( - "%s=\"%s\" ", arg_name.c_str(), escaped_xml_value.c_str()); -} - -struct OmahaAppData { - string id; - string version; - string product_components; -}; - -bool IsValidComponentID(const string& id) { - for (char c : id) { - if (!isalnum(c) && c != '-' && c != '_' && c != '.') - return false; - } - return true; -} - -// Returns an XML that corresponds to the entire <app> node of the Omaha -// request based on the given parameters. -string GetAppXml(const OmahaEvent* event, - OmahaRequestParams* params, - const OmahaAppData& app_data, - bool ping_only, - bool include_ping, - bool skip_updatecheck, - int ping_active_days, - int ping_roll_call_days, - int install_date_in_days, - SystemState* system_state) { - string app_body = GetAppBody(event, - params, - ping_only, - include_ping, - skip_updatecheck, - ping_active_days, - ping_roll_call_days, - system_state->prefs()); - string app_versions; - - // If we are downgrading to a more stable channel and we are allowed to do - // powerwash, then pass 0.0.0.0 as the version. This is needed to get the - // highest-versioned payload on the destination channel. - if (params->ShouldPowerwash()) { - LOG(INFO) << "Passing OS version as 0.0.0.0 as we are set to powerwash " - << "on downgrading to the version in the more stable channel"; - app_versions = "version=\"0.0.0.0\" from_version=\"" + - XmlEncodeWithDefault(app_data.version, "0.0.0.0") + "\" "; - } else { - app_versions = "version=\"" + - XmlEncodeWithDefault(app_data.version, "0.0.0.0") + "\" "; - } - - string download_channel = params->download_channel(); - string app_channels = - "track=\"" + XmlEncodeWithDefault(download_channel, "") + "\" "; - if (params->current_channel() != download_channel) { - app_channels += "from_track=\"" + - XmlEncodeWithDefault(params->current_channel(), "") + "\" "; - } - - string delta_okay_str = params->delta_okay() ? "true" : "false"; - - // If install_date_days is not set (e.g. its value is -1 ), don't - // include the attribute. - string install_date_in_days_str = ""; - if (install_date_in_days >= 0) { - install_date_in_days_str = - base::StringPrintf("installdate=\"%d\" ", install_date_in_days); - } - - string app_cohort_args; - app_cohort_args += - GetCohortArgXml(system_state->prefs(), "cohort", kPrefsOmahaCohort); - app_cohort_args += GetCohortArgXml( - system_state->prefs(), "cohorthint", kPrefsOmahaCohortHint); - app_cohort_args += GetCohortArgXml( - system_state->prefs(), "cohortname", kPrefsOmahaCohortName); - - string fingerprint_arg; - if (!params->os_build_fingerprint().empty()) { - fingerprint_arg = "fingerprint=\"" + - XmlEncodeWithDefault(params->os_build_fingerprint(), "") + - "\" "; - } - - string buildtype_arg; - if (!params->os_build_type().empty()) { - buildtype_arg = "os_build_type=\"" + - XmlEncodeWithDefault(params->os_build_type(), "") + "\" "; - } - - string product_components_args; - if (!params->ShouldPowerwash() && !app_data.product_components.empty()) { - brillo::KeyValueStore store; - if (store.LoadFromString(app_data.product_components)) { - for (const string& key : store.GetKeys()) { - if (!IsValidComponentID(key)) { - LOG(ERROR) << "Invalid component id: " << key; - continue; - } - string version; - if (!store.GetString(key, &version)) { - LOG(ERROR) << "Failed to get version for " << key - << " in product_components."; - continue; - } - product_components_args += - base::StringPrintf("_%s.version=\"%s\" ", - key.c_str(), - XmlEncodeWithDefault(version, "").c_str()); - } - } else { - LOG(ERROR) << "Failed to parse product_components:\n" - << app_data.product_components; - } - } - - // clang-format off - string app_xml = " <app " - "appid=\"" + XmlEncodeWithDefault(app_data.id, "") + "\" " + - app_cohort_args + - app_versions + - app_channels + - product_components_args + - fingerprint_arg + - buildtype_arg + - "lang=\"" + XmlEncodeWithDefault(params->app_lang(), "en-US") + "\" " + - "board=\"" + XmlEncodeWithDefault(params->os_board(), "") + "\" " + - "hardware_class=\"" + XmlEncodeWithDefault(params->hwid(), "") + "\" " + - "delta_okay=\"" + delta_okay_str + "\" " - "fw_version=\"" + XmlEncodeWithDefault(params->fw_version(), "") + "\" " + - "ec_version=\"" + XmlEncodeWithDefault(params->ec_version(), "") + "\" " + - install_date_in_days_str + - ">\n" + - app_body + - " </app>\n"; - // clang-format on - return app_xml; -} - -// Returns an XML that corresponds to the entire <os> node of the Omaha -// request based on the given parameters. -string GetOsXml(OmahaRequestParams* params) { - string os_xml = - " <os " - "version=\"" + - XmlEncodeWithDefault(params->os_version(), "") + "\" " + "platform=\"" + - XmlEncodeWithDefault(params->os_platform(), "") + "\" " + "sp=\"" + - XmlEncodeWithDefault(params->os_sp(), "") + - "\">" - "</os>\n"; - return os_xml; -} - -// Returns an XML that corresponds to the entire Omaha request based on the -// given parameters. -string GetRequestXml(const OmahaEvent* event, - OmahaRequestParams* params, - bool ping_only, - bool include_ping, - int ping_active_days, - int ping_roll_call_days, - int install_date_in_days, - SystemState* system_state) { - string os_xml = GetOsXml(params); - OmahaAppData product_app = { - .id = params->GetAppId(), - .version = params->app_version(), - .product_components = params->product_components()}; - // Skips updatecheck for platform app in case of an install operation. - string app_xml = GetAppXml(event, - params, - product_app, - ping_only, - include_ping, - params->is_install(), /* skip_updatecheck */ - ping_active_days, - ping_roll_call_days, - install_date_in_days, - system_state); - if (!params->system_app_id().empty()) { - OmahaAppData system_app = {.id = params->system_app_id(), - .version = params->system_version()}; - app_xml += GetAppXml(event, - params, - system_app, - ping_only, - include_ping, - false, /* skip_updatecheck */ - ping_active_days, - ping_roll_call_days, - install_date_in_days, - system_state); - } - // Create APP ID according to |dlc_module_id| (sticking the current AppID to - // the DLC module ID with an underscode). - for (const auto& dlc_module_id : params->dlc_module_ids()) { - OmahaAppData dlc_module_app = { - .id = params->GetAppId() + "_" + dlc_module_id, - .version = params->app_version()}; - app_xml += GetAppXml(event, - params, - dlc_module_app, - ping_only, - include_ping, - false, /* skip_updatecheck */ - ping_active_days, - ping_roll_call_days, - install_date_in_days, - system_state); - } - - string install_source = base::StringPrintf( - "installsource=\"%s\" ", - (params->interactive() ? "ondemandupdate" : "scheduler")); - - string updater_version = XmlEncodeWithDefault( - base::StringPrintf( - "%s-%s", constants::kOmahaUpdaterID, kOmahaUpdaterVersion), - ""); - string request_xml = - "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - "<request protocol=\"3.0\" " + - ("version=\"" + updater_version + - "\" " - "updaterversion=\"" + - updater_version + "\" " + install_source + "ismachine=\"1\">\n") + - os_xml + app_xml + "</request>\n"; - - return request_xml; -} - -} // namespace +// updatecheck attributes. +// Deprecated: "eol" +constexpr char kAttrEolDate[] = "_eol_date"; +constexpr char kAttrRollback[] = "_rollback"; +constexpr char kAttrFirmwareVersion[] = "_firmware_version"; +constexpr char kAttrKernelVersion[] = "_kernel_version"; // Struct used for holding data obtained when parsing the XML. struct OmahaParserData { - explicit OmahaParserData(XML_Parser _xml_parser) : xml_parser(_xml_parser) {} + OmahaParserData(XML_Parser _xml_parser, int _rollback_allowed_milestones) + : xml_parser(_xml_parser), + rollback_allowed_milestones(_rollback_allowed_milestones) {} // Pointer to the expat XML_Parser object. XML_Parser xml_parser; + // Some values that we need during parsing. + int rollback_allowed_milestones; + // This is the state of the parser as it's processing the XML. bool failed = false; bool entity_decl = false; string current_path; // These are the values extracted from the XML. - string updatecheck_poll_interval; - map<string, string> updatecheck_attrs; - string daystart_elapsed_days; - string daystart_elapsed_seconds; + struct DayStart { + string elapsed_days; + string elapsed_seconds; + } daystart; struct App { string id; - vector<string> url_codebase; - string manifest_version; - map<string, string> action_postinstall_attrs; - string updatecheck_status; - string cohort; - string cohorthint; - string cohortname; - bool cohort_set = false; - bool cohorthint_set = false; - bool cohortname_set = false; + Optional<string> cohort; + Optional<string> cohorthint; + Optional<string> cohortname; + + struct Url { + string codebase; + }; + vector<Url> urls; + + struct Manifest { + string version; + } manifest; + + struct UpdateCheck { + string status; + string poll_interval; + string eol_date; + string rollback; + string firmware_version; + string kernel_version; + string past_firmware_version; + string past_kernel_version; + } updatecheck; + + struct PostInstallAction { + vector<string> is_delta_payloads; + vector<string> metadata_signature_rsas; + vector<string> metadata_sizes; + string max_days_to_scatter; + string no_update; + string more_info_url; + string prompt; + string deadline; + string disable_p2p_for_downloading; + string disable_p2p_for_sharing; + string public_key_rsa; + string max_failure_count_per_url; + string disable_payload_backoff; + string powerwash_required; + }; + Optional<PostInstallAction> postinstall_action; struct Package { string name; string size; string hash; + string fp; }; vector<Package> packages; }; @@ -545,59 +220,75 @@ void ParserHandlerStart(void* user_data, } } - if (data->current_path == "/response/app") { - OmahaParserData::App app; - if (attrs.find(kAttrAppId) != attrs.end()) { - app.id = attrs[kAttrAppId]; - } - if (attrs.find(kAttrCohort) != attrs.end()) { - app.cohort_set = true; - app.cohort = attrs[kAttrCohort]; - } - if (attrs.find(kAttrCohortHint) != attrs.end()) { - app.cohorthint_set = true; - app.cohorthint = attrs[kAttrCohortHint]; - } - if (attrs.find(kAttrCohortName) != attrs.end()) { - app.cohortname_set = true; - app.cohortname = attrs[kAttrCohortName]; - } - data->apps.push_back(std::move(app)); + if (data->current_path == "/response/daystart") { + data->daystart = { + .elapsed_days = attrs[kAttrElapsedDays], + .elapsed_seconds = attrs[kAttrElapsedSeconds], + }; + } else if (data->current_path == "/response/app") { + data->apps.push_back({.id = attrs[kAttrAppId]}); + if (attrs.find(kAttrCohort) != attrs.end()) + data->apps.back().cohort = attrs[kAttrCohort]; + if (attrs.find(kAttrCohortHint) != attrs.end()) + data->apps.back().cohorthint = attrs[kAttrCohortHint]; + if (attrs.find(kAttrCohortName) != attrs.end()) + data->apps.back().cohortname = attrs[kAttrCohortName]; } else if (data->current_path == "/response/app/updatecheck") { - if (!data->apps.empty()) - data->apps.back().updatecheck_status = attrs[kAttrStatus]; - if (data->updatecheck_poll_interval.empty()) - data->updatecheck_poll_interval = attrs[kAttrPollInterval]; - // Omaha sends arbitrary key-value pairs as extra attributes starting with - // an underscore. - for (const auto& attr : attrs) { - if (!attr.first.empty() && attr.first[0] == '_') - data->updatecheck_attrs[attr.first.substr(1)] = attr.second; - } - } else if (data->current_path == "/response/daystart") { - // Get the install-date. - data->daystart_elapsed_days = attrs[kAttrElapsedDays]; - data->daystart_elapsed_seconds = attrs[kAttrElapsedSeconds]; + data->apps.back().updatecheck = { + .status = attrs[kAttrStatus], + .poll_interval = attrs[kAttrPollInterval], + .eol_date = attrs[kAttrEolDate], + .rollback = attrs[kAttrRollback], + .firmware_version = attrs[kAttrFirmwareVersion], + .kernel_version = attrs[kAttrKernelVersion], + .past_firmware_version = attrs[base::StringPrintf( + "%s_%i", kAttrFirmwareVersion, data->rollback_allowed_milestones)], + .past_kernel_version = attrs[base::StringPrintf( + "%s_%i", kAttrKernelVersion, data->rollback_allowed_milestones)], + }; } else if (data->current_path == "/response/app/updatecheck/urls/url") { - // Look at all <url> elements. - if (!data->apps.empty()) - data->apps.back().url_codebase.push_back(attrs[kAttrCodeBase]); + data->apps.back().urls.push_back({.codebase = attrs[kAttrCodeBase]}); } else if (data->current_path == "/response/app/updatecheck/manifest/packages/package") { - // Look at all <package> elements. - if (!data->apps.empty()) - data->apps.back().packages.push_back({.name = attrs[kAttrName], - .size = attrs[kAttrSize], - .hash = attrs[kAttrHashSha256]}); + data->apps.back().packages.push_back({ + .name = attrs[kAttrName], + .size = attrs[kAttrSize], + .hash = attrs[kAttrHashSha256], + .fp = attrs[kAttrFp], + }); } else if (data->current_path == "/response/app/updatecheck/manifest") { - // Get the version. - if (!data->apps.empty()) - data->apps.back().manifest_version = attrs[kAttrVersion]; + data->apps.back().manifest.version = attrs[kAttrVersion]; } else if (data->current_path == "/response/app/updatecheck/manifest/actions/action") { // We only care about the postinstall action. - if (attrs[kAttrEvent] == kValPostInstall && !data->apps.empty()) { - data->apps.back().action_postinstall_attrs = std::move(attrs); + if (attrs[kAttrEvent] == kValPostInstall) { + OmahaParserData::App::PostInstallAction action = { + .is_delta_payloads = base::SplitString(attrs[kAttrIsDeltaPayload], + ":", + base::TRIM_WHITESPACE, + base::SPLIT_WANT_ALL), + .metadata_signature_rsas = + base::SplitString(attrs[kAttrMetadataSignatureRsa], + ":", + base::TRIM_WHITESPACE, + base::SPLIT_WANT_ALL), + .metadata_sizes = base::SplitString(attrs[kAttrMetadataSize], + ":", + base::TRIM_WHITESPACE, + base::SPLIT_WANT_ALL), + .max_days_to_scatter = attrs[kAttrMaxDaysToScatter], + .no_update = attrs[kAttrNoUpdate], + .more_info_url = attrs[kAttrMoreInfo], + .prompt = attrs[kAttrPrompt], + .deadline = attrs[kAttrDeadline], + .disable_p2p_for_downloading = attrs[kAttrDisableP2PForDownloading], + .disable_p2p_for_sharing = attrs[kAttrDisableP2PForSharing], + .public_key_rsa = attrs[kAttrPublicKeyRsa], + .max_failure_count_per_url = attrs[kAttrMaxFailureCountPerUrl], + .disable_payload_backoff = attrs[kAttrDisablePayloadBackoff], + .powerwash_required = attrs[kAttrPowerwash], + }; + data->apps.back().postinstall_action = std::move(action); } } } @@ -645,62 +336,18 @@ void ParserHandlerEntityDecl(void* user_data, } // namespace -bool XmlEncode(const string& input, string* output) { - if (std::find_if(input.begin(), input.end(), [](const char c) { - return c & 0x80; - }) != input.end()) { - LOG(WARNING) << "Invalid ASCII-7 string passed to the XML encoder:"; - utils::HexDumpString(input); - return false; - } - output->clear(); - // We need at least input.size() space in the output, but the code below will - // handle it if we need more. - output->reserve(input.size()); - for (char c : input) { - switch (c) { - case '\"': - output->append("""); - break; - case '\'': - output->append("'"); - break; - case '&': - output->append("&"); - break; - case '<': - output->append("<"); - break; - case '>': - output->append(">"); - break; - default: - output->push_back(c); - } - } - return true; -} - -string XmlEncodeWithDefault(const string& input, const string& default_value) { - string output; - if (XmlEncode(input, &output)) - return output; - return default_value; -} - OmahaRequestAction::OmahaRequestAction( - SystemState* system_state, OmahaEvent* event, std::unique_ptr<HttpFetcher> http_fetcher, - bool ping_only) - : system_state_(system_state), - params_(system_state->request_params()), - event_(event), + bool ping_only, + const string& session_id) + : event_(event), http_fetcher_(std::move(http_fetcher)), policy_provider_(std::make_unique<policy::PolicyProvider>()), ping_only_(ping_only), ping_active_days_(0), - ping_roll_call_days_(0) { + ping_roll_call_days_(0), + session_id_(session_id) { policy_provider_->Reload(); } @@ -708,9 +355,10 @@ OmahaRequestAction::~OmahaRequestAction() {} // Calculates the value to use for the ping days parameter. int OmahaRequestAction::CalculatePingDays(const string& key) { - int days = kNeverPinged; + int days = kPingNeverPinged; int64_t last_ping = 0; - if (system_state_->prefs()->GetInt64(key, &last_ping) && last_ping >= 0) { + if (SystemState::Get()->prefs()->GetInt64(key, &last_ping) && + last_ping >= 0) { days = (Time::Now() - Time::FromInternalValue(last_ping)).InDays(); if (days < 0) { // If |days| is negative, then the system clock must have jumped @@ -739,17 +387,17 @@ void OmahaRequestAction::InitPingDays() { } bool OmahaRequestAction::ShouldPing() const { - if (ping_active_days_ == OmahaRequestAction::kNeverPinged && - ping_roll_call_days_ == OmahaRequestAction::kNeverPinged) { - int powerwash_count = system_state_->hardware()->GetPowerwashCount(); + if (ping_active_days_ == kPingNeverPinged && + ping_roll_call_days_ == kPingNeverPinged) { + int powerwash_count = SystemState::Get()->hardware()->GetPowerwashCount(); if (powerwash_count > 0) { LOG(INFO) << "Not sending ping with a=-1 r=-1 to omaha because " << "powerwash_count is " << powerwash_count; return false; } - if (system_state_->hardware()->GetFirstActiveOmahaPingSent()) { + if (SystemState::Get()->hardware()->GetFirstActiveOmahaPingSent()) { LOG(INFO) << "Not sending ping with a=-1 r=-1 to omaha because " - << "the first_active_omaha_ping_sent is true"; + << "the first_active_omaha_ping_sent is true."; return false; } return true; @@ -758,15 +406,12 @@ bool OmahaRequestAction::ShouldPing() const { } // static -int OmahaRequestAction::GetInstallDate(SystemState* system_state) { - PrefsInterface* prefs = system_state->prefs(); - if (prefs == nullptr) - return -1; - +int OmahaRequestAction::GetInstallDate() { + auto* prefs = SystemState::Get()->prefs(); // 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; @@ -795,8 +440,8 @@ int OmahaRequestAction::GetInstallDate(SystemState* system_state) { // inspecting the timestamp of when OOBE happened. Time time_of_oobe; - if (!system_state->hardware()->IsOOBEEnabled() || - !system_state->hardware()->IsOOBEComplete(&time_of_oobe)) { + if (!SystemState::Get()->hardware()->IsOOBEEnabled() || + !SystemState::Get()->hardware()->IsOOBEComplete(&time_of_oobe)) { LOG(INFO) << "Not generating Omaha InstallData as we have " << "no prefs file and OOBE is not complete or not enabled."; return -1; @@ -811,16 +456,56 @@ int OmahaRequestAction::GetInstallDate(SystemState* system_state) { } // Persist this to disk, for future use. - if (!OmahaRequestAction::PersistInstallDate( - system_state, num_days, kProvisionedFromOOBEMarker)) + if (!OmahaRequestAction::PersistInstallDate(num_days, + kProvisionedFromOOBEMarker)) return -1; LOG(INFO) << "Set the Omaha InstallDate from OOBE time-stamp to " << num_days - << " days"; + << " days."; return num_days; } +void OmahaRequestAction::StorePingReply( + const OmahaParserData& parser_data) const { + const auto* params = SystemState::Get()->request_params(); + for (const auto& app : parser_data.apps) { + auto it = params->dlc_apps_params().find(app.id); + if (it == params->dlc_apps_params().end()) + continue; + + const OmahaRequestParams::AppParams& dlc_params = it->second; + const string& dlc_id = dlc_params.name; + // Skip if the ping for this DLC was not sent. + if (!dlc_params.send_ping) + continue; + + auto* prefs = SystemState::Get()->prefs(); + // Reset the active metadata value to |kPingInactiveValue|. + auto active_key = + prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive}); + if (!prefs->SetInt64(active_key, kPingInactiveValue)) + LOG(ERROR) << "Failed to set the value of ping metadata '" << active_key + << "'."; + + auto last_rollcall_key = + prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingLastRollcall}); + if (!prefs->SetString(last_rollcall_key, parser_data.daystart.elapsed_days)) + LOG(ERROR) << "Failed to set the value of ping metadata '" + << last_rollcall_key << "'."; + + if (dlc_params.ping_active) { + // Write the value of elapsed_days into |kPrefsPingLastActive| only if + // the previous ping was an active one. + auto last_active_key = + prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingLastActive}); + if (!prefs->SetString(last_active_key, parser_data.daystart.elapsed_days)) + LOG(ERROR) << "Failed to set the value of ping metadata '" + << last_active_key << "'."; + } + } +} + void OmahaRequestAction::PerformAction() { http_fetcher_->set_delegate(this); InitPingDays(); @@ -829,19 +514,20 @@ void OmahaRequestAction::PerformAction() { return; } - string request_post(GetRequestXml(event_.get(), - params_, - ping_only_, - ShouldPing(), // include_ping - ping_active_days_, - ping_roll_call_days_, - GetInstallDate(system_state_), - system_state_)); + OmahaRequestBuilderXml omaha_request(event_.get(), + ping_only_, + ShouldPing(), // include_ping + ping_active_days_, + ping_roll_call_days_, + GetInstallDate(), + session_id_); + string request_post = omaha_request.GetRequest(); // Set X-Goog-Update headers. + const auto* params = SystemState::Get()->request_params(); http_fetcher_->SetHeader(kXGoogleUpdateInteractivity, - params_->interactive() ? "fg" : "bg"); - http_fetcher_->SetHeader(kXGoogleUpdateAppId, params_->GetAppId()); + params->interactive() ? "fg" : "bg"); + http_fetcher_->SetHeader(kXGoogleUpdateAppId, params->GetAppId()); http_fetcher_->SetHeader( kXGoogleUpdateUpdater, base::StringPrintf( @@ -849,9 +535,9 @@ void OmahaRequestAction::PerformAction() { http_fetcher_->SetPostData( request_post.data(), request_post.size(), kHttpContentTypeTextXml); - LOG(INFO) << "Posting an Omaha request to " << params_->update_url(); + LOG(INFO) << "Posting an Omaha request to " << params->update_url(); LOG(INFO) << "Request: " << request_post; - http_fetcher_->BeginTransfer(params_->update_url()); + http_fetcher_->BeginTransfer(params->update_url()); } void OmahaRequestAction::TerminateProcessing() { @@ -890,14 +576,15 @@ bool ParseBool(const string& str) { // Update the last ping day preferences based on the server daystart // response. Returns true on success, false otherwise. -bool UpdateLastPingDays(OmahaParserData* parser_data, PrefsInterface* prefs) { +bool UpdateLastPingDays(OmahaParserData* parser_data) { int64_t elapsed_seconds = 0; TEST_AND_RETURN_FALSE(base::StringToInt64( - parser_data->daystart_elapsed_seconds, &elapsed_seconds)); + parser_data->daystart.elapsed_seconds, &elapsed_seconds)); TEST_AND_RETURN_FALSE(elapsed_seconds >= 0); // Remember the local time that matches the server's last midnight // time. + auto* prefs = SystemState::Get()->prefs(); Time daystart = Time::Now() - TimeDelta::FromSeconds(elapsed_seconds); prefs->SetInt64(kPrefsLastActivePingDay, daystart.ToInternalValue()); prefs->SetInt64(kPrefsLastRollCallPingDay, daystart.ToInternalValue()); @@ -909,9 +596,10 @@ bool UpdateLastPingDays(OmahaParserData* parser_data, PrefsInterface* prefs) { // False otherwise, in which case it sets any error code using |completer|. bool ParsePackage(OmahaParserData::App* app, OmahaResponse* output_object, + bool can_exclude, ScopedActionCompleter* completer) { - if (app->updatecheck_status.empty() || - app->updatecheck_status == kValNoUpdate) { + if (app->updatecheck.status.empty() || + app->updatecheck.status == kValNoUpdate) { if (!app->packages.empty()) { LOG(ERROR) << "No update in this <app> but <package> is not empty."; completer->set_code(ErrorCode::kOmahaResponseInvalid); @@ -920,79 +608,62 @@ bool ParsePackage(OmahaParserData::App* app, return true; } if (app->packages.empty()) { - LOG(ERROR) << "Omaha Response has no packages"; + LOG(ERROR) << "Omaha Response has no packages."; completer->set_code(ErrorCode::kOmahaResponseInvalid); return false; } - if (app->url_codebase.empty()) { - LOG(ERROR) << "No Omaha Response URLs"; + if (app->urls.empty()) { + LOG(ERROR) << "No Omaha Response URLs."; completer->set_code(ErrorCode::kOmahaResponseInvalid); return false; } - LOG(INFO) << "Found " << app->url_codebase.size() << " url(s)"; - vector<string> metadata_sizes = - base::SplitString(app->action_postinstall_attrs[kAttrMetadataSize], - ":", - base::TRIM_WHITESPACE, - base::SPLIT_WANT_ALL); - vector<string> metadata_signatures = base::SplitString( - app->action_postinstall_attrs[kAttrMetadataSignatureRsa], - ":", - base::TRIM_WHITESPACE, - base::SPLIT_WANT_ALL); - vector<string> is_delta_payloads = - base::SplitString(app->action_postinstall_attrs[kAttrIsDeltaPayload], - ":", - base::TRIM_WHITESPACE, - base::SPLIT_WANT_ALL); for (size_t i = 0; i < app->packages.size(); i++) { const auto& package = app->packages[i]; if (package.name.empty()) { - LOG(ERROR) << "Omaha Response has empty package name"; + LOG(ERROR) << "Omaha Response has empty package name."; completer->set_code(ErrorCode::kOmahaResponseInvalid); return false; } - LOG(INFO) << "Found package " << package.name; OmahaResponse::Package out_package; - for (const string& codebase : app->url_codebase) { - if (codebase.empty()) { - LOG(ERROR) << "Omaha Response URL has empty codebase"; + out_package.app_id = app->id; + out_package.can_exclude = can_exclude; + for (const auto& url : app->urls) { + if (url.codebase.empty()) { + LOG(ERROR) << "Omaha Response URL has empty codebase."; completer->set_code(ErrorCode::kOmahaResponseInvalid); return false; } - out_package.payload_urls.push_back(codebase + package.name); + out_package.payload_urls.push_back(url.codebase + package.name); } - // Parse the payload size. + base::StringToUint64(package.size, &out_package.size); if (out_package.size <= 0) { LOG(ERROR) << "Omaha Response has invalid payload size: " << package.size; completer->set_code(ErrorCode::kOmahaResponseInvalid); return false; } - LOG(INFO) << "Payload size = " << out_package.size << " bytes"; - if (i < metadata_sizes.size()) - base::StringToUint64(metadata_sizes[i], &out_package.metadata_size); - LOG(INFO) << "Payload metadata size = " << out_package.metadata_size - << " bytes"; + if (i < app->postinstall_action->metadata_sizes.size()) + base::StringToUint64(app->postinstall_action->metadata_sizes[i], + &out_package.metadata_size); - if (i < metadata_signatures.size()) - out_package.metadata_signature = metadata_signatures[i]; - LOG(INFO) << "Payload metadata signature = " - << out_package.metadata_signature; + if (i < app->postinstall_action->metadata_signature_rsas.size()) + out_package.metadata_signature = + app->postinstall_action->metadata_signature_rsas[i]; out_package.hash = package.hash; if (out_package.hash.empty()) { - LOG(ERROR) << "Omaha Response has empty hash_sha256 value"; + LOG(ERROR) << "Omaha Response has empty hash_sha256 value."; completer->set_code(ErrorCode::kOmahaResponseInvalid); return false; } - LOG(INFO) << "Payload hash = " << out_package.hash; - if (i < is_delta_payloads.size()) - out_package.is_delta = ParseBool(is_delta_payloads[i]); - LOG(INFO) << "Payload is delta = " << utils::ToString(out_package.is_delta); + out_package.fp = package.fp; + + if (i < app->postinstall_action->is_delta_payloads.size()) + out_package.is_delta = + ParseBool(app->postinstall_action->is_delta_payloads[i]); output_object->packages.push_back(std::move(out_package)); } @@ -1000,18 +671,87 @@ bool ParsePackage(OmahaParserData::App* app, return true; } +// Removes the candidate URLs which are excluded within packages, if all the +// candidate URLs are excluded within a package, the package will be excluded. +void ProcessExclusions(OmahaResponse* output_object, + OmahaRequestParams* params, + ExcluderInterface* excluder) { + for (auto package_it = output_object->packages.begin(); + package_it != output_object->packages.end(); + /* Increment logic in loop */) { + // If package cannot be excluded, quickly continue. + if (!package_it->can_exclude) { + ++package_it; + continue; + } + // Remove the excluded payload URLs. + for (auto payload_url_it = package_it->payload_urls.begin(); + payload_url_it != package_it->payload_urls.end(); + /* Increment logic in loop */) { + auto exclusion_name = utils::GetExclusionName(*payload_url_it); + // If payload URL is not excluded, quickly continue. + if (!excluder->IsExcluded(exclusion_name)) { + ++payload_url_it; + continue; + } + LOG(INFO) << "Excluding payload URL=" << *payload_url_it + << " for payload hash=" << package_it->hash; + payload_url_it = package_it->payload_urls.erase(payload_url_it); + } + // If there are no candidate payload URLs, remove the package. + if (package_it->payload_urls.empty()) { + LOG(INFO) << "Excluding payload hash=" << package_it->hash; + // Need to set DLC as not updated so correct metrics can be sent when an + // update is completed. + params->SetDlcNoUpdate(package_it->app_id); + package_it = output_object->packages.erase(package_it); + continue; + } + ++package_it; + } +} + // Parses the 2 key version strings kernel_version and firmware_version. If the // field is not present, or cannot be parsed the values default to 0xffff. -void ParseRollbackVersions(OmahaParserData* parser_data, +void ParseRollbackVersions(const OmahaParserData::App& platform_app, + int allowed_milestones, OmahaResponse* output_object) { + // Defaults to false if attribute is not present. + output_object->is_rollback = ParseBool(platform_app.updatecheck.rollback); + utils::ParseRollbackKeyVersion( - parser_data->updatecheck_attrs[kAttrFirmwareVersion], + platform_app.updatecheck.firmware_version, &output_object->rollback_key_version.firmware_key, &output_object->rollback_key_version.firmware); utils::ParseRollbackKeyVersion( - parser_data->updatecheck_attrs[kAttrKernelVersion], + platform_app.updatecheck.kernel_version, &output_object->rollback_key_version.kernel_key, &output_object->rollback_key_version.kernel); + + string firmware_version = platform_app.updatecheck.past_firmware_version; + string kernel_version = platform_app.updatecheck.past_kernel_version; + + LOG(INFO) << "For milestone N-" << allowed_milestones + << " firmware_key_version=" << firmware_version + << " kernel_key_version=" << kernel_version; + + OmahaResponse::RollbackKeyVersion version; + utils::ParseRollbackKeyVersion( + firmware_version, &version.firmware_key, &version.firmware); + utils::ParseRollbackKeyVersion( + kernel_version, &version.kernel_key, &version.kernel); + + output_object->past_rollback_key_version = std::move(version); +} + +void PersistEolInfo(const OmahaParserData::App& platform_app) { + // If EOL date attribute is not sent, don't delete the old persisted EOL + // date information. + if (!platform_app.updatecheck.eol_date.empty() && + !SystemState::Get()->prefs()->SetString( + kPrefsOmahaEolDate, platform_app.updatecheck.eol_date)) { + LOG(ERROR) << "Setting EOL date failed."; + } } } // namespace @@ -1023,67 +763,63 @@ bool OmahaRequestAction::ParseResponse(OmahaParserData* parser_data, completer->set_code(ErrorCode::kOmahaResponseInvalid); return false; } - LOG(INFO) << "Found " << parser_data->apps.size() << " <app>."; - - // chromium-os:37289: The PollInterval is not supported by Omaha server - // currently. But still keeping this existing code in case we ever decide to - // slow down the request rate from the server-side. Note that the PollInterval - // is not persisted, so it has to be sent by the server on every response to - // guarantee that the scheduler uses this value (otherwise, if the device got - // rebooted after the last server-indicated value, it'll revert to the default - // value). Also kDefaultMaxUpdateChecks value for the scattering logic is - // based on the assumption that we perform an update check every hour so that - // the max value of 8 will roughly be equivalent to one work day. If we decide - // to use PollInterval permanently, we should update the - // max_update_checks_allowed to take PollInterval into account. Note: The - // parsing for PollInterval happens even before parsing of the status because - // we may want to specify the PollInterval even when there's no update. - base::StringToInt(parser_data->updatecheck_poll_interval, - &output_object->poll_interval); + + // Locate the platform App since it's an important one that has specific + // information attached to it that may not be available from other Apps. + const auto* params = SystemState::Get()->request_params(); + auto platform_app = std::find_if(parser_data->apps.begin(), + parser_data->apps.end(), + [¶ms](const OmahaParserData::App& app) { + return app.id == params->GetAppId(); + }); + if (platform_app == parser_data->apps.end()) { + LOG(WARNING) << "Platform App is missing."; + } else { + // chromium-os:37289: The PollInterval is not supported by Omaha server + // currently. But still keeping this existing code in case we ever decide + // to slow down the request rate from the server-side. Note that the + // PollInterval is not persisted, so it has to be sent by the server on + // every response to guarantee that the scheduler uses this value + // (otherwise, if the device got rebooted after the last server-indicated + // value, it'll revert to the default value). Also kDefaultMaxUpdateChecks + // value for the scattering logic is based on the assumption that we perform + // an update check every hour so that the max value of 8 will roughly be + // equivalent to one work day. If we decide to use PollInterval permanently, + // we should update the max_update_checks_allowed to take PollInterval into + // account. Note: The parsing for PollInterval happens even before parsing + // of the status because we may want to specify the PollInterval even when + // there's no update. + base::StringToInt(platform_app->updatecheck.poll_interval, + &output_object->poll_interval); + + PersistEolInfo(*platform_app); + + // Parses the rollback versions of the current image. If the fields do not + // exist they default to 0xffff for the 4 key versions. + ParseRollbackVersions( + *platform_app, params->rollback_allowed_milestones(), output_object); + } // Check for the "elapsed_days" attribute in the "daystart" // element. This is the number of days since Jan 1 2007, 0:00 // PST. If we don't have a persisted value of the Omaha InstallDate, // we'll use it to calculate it and then persist it. - if (ParseInstallDate(parser_data, output_object) && - !HasInstallDate(system_state_)) { + if (ParseInstallDate(parser_data, output_object) && !HasInstallDate()) { // Since output_object->install_date_days is never negative, the // elapsed_days -> install-date calculation is reduced to simply // rounding down to the nearest number divisible by 7. int remainder = output_object->install_date_days % 7; int install_date_days_rounded = output_object->install_date_days - remainder; - if (PersistInstallDate(system_state_, - install_date_days_rounded, + if (PersistInstallDate(install_date_days_rounded, kProvisionedFromOmahaResponse)) { LOG(INFO) << "Set the Omaha InstallDate from Omaha Response to " - << install_date_days_rounded << " days"; + << install_date_days_rounded << " days."; } } // We persist the cohorts sent by omaha even if the status is "noupdate". - for (const auto& app : parser_data->apps) { - if (app.id == params_->GetAppId()) { - if (app.cohort_set) - PersistCohortData(kPrefsOmahaCohort, app.cohort); - if (app.cohorthint_set) - PersistCohortData(kPrefsOmahaCohortHint, app.cohorthint); - if (app.cohortname_set) - PersistCohortData(kPrefsOmahaCohortName, app.cohortname); - break; - } - } - - // Parse the updatecheck attributes. - PersistEolStatus(parser_data->updatecheck_attrs); - // Rollback-related updatecheck attributes. - // Defaults to false if attribute is not present. - output_object->is_rollback = - ParseBool(parser_data->updatecheck_attrs[kAttrRollback]); - - // Parses the rollback versions of the current image. If the fields do not - // exist they default to 0xffff for the 4 key versions. - ParseRollbackVersions(parser_data, output_object); + PersistCohorts(*parser_data); if (!ParseStatus(parser_data, output_object, completer)) return false; @@ -1093,9 +829,15 @@ bool OmahaRequestAction::ParseResponse(OmahaParserData* parser_data, // Package has to be parsed after Params now because ParseParams need to make // sure that postinstall action exists. - for (auto& app : parser_data->apps) - if (!ParsePackage(&app, output_object, completer)) + for (auto& app : parser_data->apps) { + // Only allow exclusions for a non-critical package during an update. For + // non-critical package installations, let the errors propagate instead + // of being handled inside update_engine as installations are a dlcservice + // specific feature. + bool can_exclude = !params->is_install() && params->IsDlcAppId(app.id); + if (!ParsePackage(&app, output_object, can_exclude, completer)) return false; + } return true; } @@ -1104,29 +846,34 @@ bool OmahaRequestAction::ParseStatus(OmahaParserData* parser_data, OmahaResponse* output_object, ScopedActionCompleter* completer) { output_object->update_exists = false; + auto* params = SystemState::Get()->request_params(); for (const auto& app : parser_data->apps) { - const string& status = app.updatecheck_status; + const string& status = app.updatecheck.status; if (status == kValNoUpdate) { + // If the app is a DLC, allow status "noupdate" to support DLC + // deprecations. + if (params->IsDlcAppId(app.id)) { + LOG(INFO) << "No update for App " << app.id + << " but update continuing since a DLC."; + params->SetDlcNoUpdate(app.id); + continue; + } // Don't update if any app has status="noupdate". - LOG(INFO) << "No update for <app> " << app.id; + LOG(INFO) << "No update for App " << app.id; output_object->update_exists = false; break; } else if (status == "ok") { - auto const& attr_no_update = - app.action_postinstall_attrs.find(kAttrNoUpdate); - if (attr_no_update != app.action_postinstall_attrs.end() && - attr_no_update->second == "true") { + if (ParseBool(app.postinstall_action->no_update)) { // noupdate="true" in postinstall attributes means it's an update to // self, only update if there's at least one app really have update. - LOG(INFO) << "Update to self for <app> " << app.id; + LOG(INFO) << "Update to self for App " << app.id; } else { - LOG(INFO) << "Update for <app> " << app.id; output_object->update_exists = true; } - } else if (status.empty() && params_->is_install() && - params_->GetAppId() == app.id) { + } else if (status.empty() && params->is_install() && + params->GetAppId() == app.id) { // Skips the platform app for install operation. - LOG(INFO) << "No payload (and ignore) for <app> " << app.id; + LOG(INFO) << "No payload (and ignore) for App " << app.id; } else { LOG(ERROR) << "Unknown Omaha response status: " << status; completer->set_code(ErrorCode::kOmahaResponseInvalid); @@ -1144,63 +891,57 @@ bool OmahaRequestAction::ParseStatus(OmahaParserData* parser_data, bool OmahaRequestAction::ParseParams(OmahaParserData* parser_data, OmahaResponse* output_object, ScopedActionCompleter* completer) { - map<string, string> attrs; - for (auto& app : parser_data->apps) { - if (app.id == params_->GetAppId()) { - // this is the app (potentially the only app) - output_object->version = app.manifest_version; - } else if (!params_->system_app_id().empty() && - app.id == params_->system_app_id()) { - // this is the system app (this check is intentionally skipped if there is - // no system_app_id set) - output_object->system_version = app.manifest_version; - } else if (params_->is_install() && - app.manifest_version != params_->app_version()) { - LOG(WARNING) << "An app has a different version (" << app.manifest_version - << ") that is different than platform app version (" - << params_->app_version() << ")"; + const auto* params = SystemState::Get()->request_params(); + const OmahaParserData::App* main_app = nullptr; + for (const auto& app : parser_data->apps) { + if (app.id == params->GetAppId() && app.postinstall_action) { + main_app = &app; + } else if (params->is_install()) { + if (app.manifest.version != params->app_version()) { + LOG(WARNING) << "An app has a version: " << app.manifest.version + << " that is different than platform app version: " + << params->app_version(); + } } - if (!app.action_postinstall_attrs.empty() && attrs.empty()) { - attrs = app.action_postinstall_attrs; + if (app.postinstall_action && main_app == nullptr) { + main_app = &app; } } - if (params_->is_install()) { - LOG(INFO) << "Use request version for Install operation."; - output_object->version = params_->app_version(); - } - if (output_object->version.empty()) { - LOG(ERROR) << "Omaha Response does not have version in manifest!"; - completer->set_code(ErrorCode::kOmahaResponseInvalid); - return false; - } - - LOG(INFO) << "Received omaha response to update to version " - << output_object->version; - if (attrs.empty()) { - LOG(ERROR) << "Omaha Response has no postinstall event action"; + if (main_app == nullptr) { + LOG(ERROR) << "Omaha Response has no postinstall event action."; completer->set_code(ErrorCode::kOmahaResponseInvalid); return false; } + const OmahaParserData::App& app = *main_app; // Get the optional properties one by one. - output_object->more_info_url = attrs[kAttrMoreInfo]; - output_object->prompt = ParseBool(attrs[kAttrPrompt]); - output_object->deadline = attrs[kAttrDeadline]; - output_object->max_days_to_scatter = ParseInt(attrs[kAttrMaxDaysToScatter]); + output_object->version = app.manifest.version; + output_object->more_info_url = app.postinstall_action->more_info_url; + output_object->prompt = ParseBool(app.postinstall_action->prompt); + output_object->deadline = app.postinstall_action->deadline; + output_object->max_days_to_scatter = + ParseInt(app.postinstall_action->max_days_to_scatter); output_object->disable_p2p_for_downloading = - ParseBool(attrs[kAttrDisableP2PForDownloading]); + ParseBool(app.postinstall_action->disable_p2p_for_downloading); output_object->disable_p2p_for_sharing = - ParseBool(attrs[kAttrDisableP2PForSharing]); - output_object->public_key_rsa = attrs[kAttrPublicKeyRsa]; + ParseBool(app.postinstall_action->disable_p2p_for_sharing); + output_object->public_key_rsa = app.postinstall_action->public_key_rsa; - string max = attrs[kAttrMaxFailureCountPerUrl]; - if (!base::StringToUint(max, &output_object->max_failure_count_per_url)) + if (!base::StringToUint(app.postinstall_action->max_failure_count_per_url, + &output_object->max_failure_count_per_url)) output_object->max_failure_count_per_url = kDefaultMaxFailureCountPerUrl; output_object->disable_payload_backoff = - ParseBool(attrs[kAttrDisablePayloadBackoff]); - output_object->powerwash_required = ParseBool(attrs[kAttrPowerwash]); + ParseBool(app.postinstall_action->disable_payload_backoff); + output_object->powerwash_required = + ParseBool(app.postinstall_action->powerwash_required); + + if (output_object->version.empty()) { + LOG(ERROR) << "Omaha Response does not have version in manifest!"; + completer->set_code(ErrorCode::kOmahaResponseInvalid); + return false; + } return true; } @@ -1214,7 +955,8 @@ void OmahaRequestAction::TransferComplete(HttpFetcher* fetcher, string current_response(response_buffer_.begin(), response_buffer_.end()); LOG(INFO) << "Omaha request response: " << current_response; - PayloadStateInterface* const payload_state = system_state_->payload_state(); + PayloadStateInterface* const payload_state = + SystemState::Get()->payload_state(); // Set the max kernel key version based on whether rollback is allowed. SetMaxKernelKeyVersionForRollback(); @@ -1226,12 +968,24 @@ void OmahaRequestAction::TransferComplete(HttpFetcher* fetcher, return; } + ErrorCode aux_error_code = fetcher->GetAuxiliaryErrorCode(); + if (aux_error_code != ErrorCode::kSuccess) { + metrics::DownloadErrorCode download_error_code = + metrics_utils::GetDownloadErrorCode(aux_error_code); + SystemState::Get()->metrics_reporter()->ReportUpdateCheckMetrics( + metrics::CheckResult::kUnset, + metrics::CheckReaction::kUnset, + download_error_code); + } + if (!successful) { - LOG(ERROR) << "Omaha request network transfer failed."; int code = GetHTTPResponseCode(); - // Makes sure we send sane error values. + LOG(ERROR) << "Omaha request network transfer failed with HTTPResponseCode=" + << code; + // Makes sure we send proper error values. if (code < 0 || code >= 1000) { code = 999; + LOG(WARNING) << "Converting to proper HTTPResponseCode=" << code; } completer.set_code(static_cast<ErrorCode>( static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + code)); @@ -1239,7 +993,9 @@ void OmahaRequestAction::TransferComplete(HttpFetcher* fetcher, } XML_Parser parser = XML_ParserCreate(nullptr); - OmahaParserData parser_data(parser); + OmahaParserData parser_data( + parser, + SystemState::Get()->request_params()->rollback_allowed_milestones()); XML_SetUserData(parser, &parser_data); XML_SetElementHandler(parser, ParserHandlerStart, ParserHandlerEnd); XML_SetEntityDeclHandler(parser, ParserHandlerEntityDecl); @@ -1269,7 +1025,7 @@ void OmahaRequestAction::TransferComplete(HttpFetcher* fetcher, // Update the last ping day preferences based on the server daystart response // even if we didn't send a ping. Omaha always includes the daystart in the // response, but log the error if it didn't. - LOG_IF(ERROR, !UpdateLastPingDays(&parser_data, system_state_->prefs())) + LOG_IF(ERROR, !UpdateLastPingDays(&parser_data)) << "Failed to update the last ping day preferences!"; // Sets first_active_omaha_ping_sent to true (vpd in CrOS). We only do this if @@ -1278,13 +1034,16 @@ void OmahaRequestAction::TransferComplete(HttpFetcher* fetcher, // need to check if a=-1 has been sent because older devices have already sent // their a=-1 in the past and we have to set first_active_omaha_ping_sent for // future checks. - if (!system_state_->hardware()->GetFirstActiveOmahaPingSent()) { - if (!system_state_->hardware()->SetFirstActiveOmahaPingSent()) { - system_state_->metrics_reporter()->ReportInternalErrorCode( + if (!SystemState::Get()->hardware()->GetFirstActiveOmahaPingSent()) { + if (!SystemState::Get()->hardware()->SetFirstActiveOmahaPingSent()) { + SystemState::Get()->metrics_reporter()->ReportInternalErrorCode( ErrorCode::kFirstActiveOmahaPingSentPersistenceError); } } + // Create/update the metadata files for each DLC app received. + StorePingReply(parser_data); + if (!HasOutputPipe()) { // Just set success to whether or not the http transfer succeeded, // which must be true at this point in the code. @@ -1295,6 +1054,9 @@ void OmahaRequestAction::TransferComplete(HttpFetcher* fetcher, OmahaResponse output_object; if (!ParseResponse(&parser_data, &output_object, &completer)) return; + ProcessExclusions(&output_object, + SystemState::Get()->request_params(), + SystemState::Get()->update_attempter()->GetExcluder()); output_object.update_exists = true; SetOutputObject(output_object); @@ -1358,7 +1120,7 @@ void OmahaRequestAction::TransferComplete(HttpFetcher* fetcher, void OmahaRequestAction::CompleteProcessing() { ScopedActionCompleter completer(processor_, this); OmahaResponse& output_object = const_cast<OmahaResponse&>(GetOutputObject()); - PayloadStateInterface* payload_state = system_state_->payload_state(); + PayloadStateInterface* payload_state = SystemState::Get()->payload_state(); if (ShouldDeferDownload(&output_object)) { output_object.update_exists = false; @@ -1370,7 +1132,7 @@ void OmahaRequestAction::CompleteProcessing() { if (payload_state->ShouldBackoffDownload()) { output_object.update_exists = false; LOG(INFO) << "Ignoring Omaha updates in order to backoff our retry " - << "attempts"; + << "attempts."; completer.set_code(ErrorCode::kOmahaUpdateDeferredForBackoff); return; } @@ -1380,11 +1142,11 @@ void OmahaRequestAction::CompleteProcessing() { void OmahaRequestAction::OnLookupPayloadViaP2PCompleted(const string& url) { LOG(INFO) << "Lookup complete, p2p-client returned URL '" << url << "'"; if (!url.empty()) { - system_state_->payload_state()->SetP2PUrl(url); + SystemState::Get()->payload_state()->SetP2PUrl(url); } else { LOG(INFO) << "Forcibly disabling use of p2p for downloading " << "because no suitable peer could be found."; - system_state_->payload_state()->SetUsingP2PForDownloading(false); + SystemState::Get()->payload_state()->SetUsingP2PForDownloading(false); } CompleteProcessing(); } @@ -1408,18 +1170,17 @@ void OmahaRequestAction::LookupPayloadViaP2P(const OmahaResponse& response) { int64_t manifest_signature_size = 0; int64_t next_data_offset = 0; int64_t next_data_length = 0; - if (system_state_ && - system_state_->prefs()->GetInt64(kPrefsManifestMetadataSize, - &manifest_metadata_size) && + if (SystemState::Get()->prefs()->GetInt64(kPrefsManifestMetadataSize, + &manifest_metadata_size) && manifest_metadata_size != -1 && - system_state_->prefs()->GetInt64(kPrefsManifestSignatureSize, - &manifest_signature_size) && + SystemState::Get()->prefs()->GetInt64(kPrefsManifestSignatureSize, + &manifest_signature_size) && manifest_signature_size != -1 && - system_state_->prefs()->GetInt64(kPrefsUpdateStateNextDataOffset, - &next_data_offset) && + SystemState::Get()->prefs()->GetInt64(kPrefsUpdateStateNextDataOffset, + &next_data_offset) && next_data_offset != -1 && - system_state_->prefs()->GetInt64(kPrefsUpdateStateNextDataLength, - &next_data_length)) { + SystemState::Get()->prefs()->GetInt64(kPrefsUpdateStateNextDataLength, + &next_data_length)) { minimum_size = manifest_metadata_size + manifest_signature_size + next_data_offset + next_data_length; } @@ -1430,10 +1191,10 @@ void OmahaRequestAction::LookupPayloadViaP2P(const OmahaResponse& response) { return; string file_id = utils::CalculateP2PFileId(raw_hash, response.packages[0].size); - if (system_state_->p2p_manager()) { + if (SystemState::Get()->p2p_manager()) { LOG(INFO) << "Checking if payload is available via p2p, file_id=" << file_id << " minimum_size=" << minimum_size; - system_state_->p2p_manager()->LookupUrlForFile( + SystemState::Get()->p2p_manager()->LookupUrlForFile( file_id, minimum_size, TimeDelta::FromSeconds(kMaxP2PNetworkWaitTimeSeconds), @@ -1443,7 +1204,8 @@ void OmahaRequestAction::LookupPayloadViaP2P(const OmahaResponse& response) { } bool OmahaRequestAction::ShouldDeferDownload(OmahaResponse* output_object) { - if (params_->interactive()) { + const auto* params = SystemState::Get()->request_params(); + if (params->interactive()) { LOG(INFO) << "Not deferring download because update is interactive."; return false; } @@ -1452,7 +1214,8 @@ bool OmahaRequestAction::ShouldDeferDownload(OmahaResponse* output_object) { // defer the download. This is because the download will always // happen from a peer on the LAN and we've been waiting in line for // our turn. - const PayloadStateInterface* payload_state = system_state_->payload_state(); + const PayloadStateInterface* payload_state = + SystemState::Get()->payload_state(); if (payload_state->GetUsingP2PForDownloading() && !payload_state->GetP2PUrl().empty()) { LOG(INFO) << "Download not deferred because download " @@ -1463,7 +1226,7 @@ bool OmahaRequestAction::ShouldDeferDownload(OmahaResponse* output_object) { // We should defer the downloads only if we've first satisfied the // wall-clock-based-waiting period and then the update-check-based waiting // period, if required. - if (!params_->wall_clock_based_wait_enabled()) { + if (!params->wall_clock_based_wait_enabled()) { LOG(INFO) << "Wall-clock-based waiting period is not enabled," << " so no deferring needed."; return false; @@ -1504,23 +1267,24 @@ OmahaRequestAction::IsWallClockBasedWaitingSatisfied( Time update_first_seen_at = LoadOrPersistUpdateFirstSeenAtPref(); if (update_first_seen_at == base::Time()) { LOG(INFO) << "Not scattering as UpdateFirstSeenAt value cannot be read or " - "persisted"; + "persisted."; return kWallClockWaitDoneAndUpdateCheckWaitNotRequired; } TimeDelta elapsed_time = - system_state_->clock()->GetWallclockTime() - update_first_seen_at; + SystemState::Get()->clock()->GetWallclockTime() - update_first_seen_at; TimeDelta max_scatter_period = TimeDelta::FromDays(output_object->max_days_to_scatter); int64_t staging_wait_time_in_days = 0; // Use staging and its default max value if staging is on. - if (system_state_->prefs()->GetInt64(kPrefsWallClockStagingWaitPeriod, - &staging_wait_time_in_days) && + if (SystemState::Get()->prefs()->GetInt64(kPrefsWallClockStagingWaitPeriod, + &staging_wait_time_in_days) && staging_wait_time_in_days > 0) max_scatter_period = TimeDelta::FromDays(kMaxWaitTimeStagingInDays); + const auto* params = SystemState::Get()->request_params(); LOG(INFO) << "Waiting Period = " - << utils::FormatSecs(params_->waiting_period().InSeconds()) + << utils::FormatSecs(params->waiting_period().InSeconds()) << ", Time Elapsed = " << utils::FormatSecs(elapsed_time.InSeconds()) << ", MaxDaysToScatter = " << max_scatter_period.InDays(); @@ -1530,7 +1294,7 @@ OmahaRequestAction::IsWallClockBasedWaitingSatisfied( // previous FSI, which means this update will be applied mostly in OOBE // cases. For these cases, we shouldn't scatter so as to finish the OOBE // quickly. - LOG(INFO) << "Not scattering as deadline flag is set"; + LOG(INFO) << "Not scattering as deadline flag is set."; return kWallClockWaitDoneAndUpdateCheckWaitNotRequired; } @@ -1551,14 +1315,14 @@ OmahaRequestAction::IsWallClockBasedWaitingSatisfied( // This means we are required to participate in scattering. // See if our turn has arrived now. - TimeDelta remaining_wait_time = params_->waiting_period() - elapsed_time; + TimeDelta remaining_wait_time = params->waiting_period() - elapsed_time; if (remaining_wait_time.InSeconds() <= 0) { // Yes, it's our turn now. LOG(INFO) << "Successfully passed the wall-clock-based-wait."; // But we can't download until the update-check-count-based wait is also // satisfied, so mark it as required now if update checks are enabled. - return params_->update_check_count_wait_enabled() + return params->update_check_count_wait_enabled() ? kWallClockWaitDoneButUpdateCheckWaitRequired : kWallClockWaitDoneAndUpdateCheckWaitNotRequired; } @@ -1573,10 +1337,11 @@ OmahaRequestAction::IsWallClockBasedWaitingSatisfied( bool OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied() { int64_t update_check_count_value; + const auto* params = SystemState::Get()->request_params(); - if (system_state_->prefs()->Exists(kPrefsUpdateCheckCount)) { - if (!system_state_->prefs()->GetInt64(kPrefsUpdateCheckCount, - &update_check_count_value)) { + if (SystemState::Get()->prefs()->Exists(kPrefsUpdateCheckCount)) { + if (!SystemState::Get()->prefs()->GetInt64(kPrefsUpdateCheckCount, + &update_check_count_value)) { // We are unable to read the update check count from file for some reason. // So let's proceed anyway so as to not stall the update. LOG(ERROR) << "Unable to read update check count. " @@ -1587,15 +1352,15 @@ bool OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied() { // This file does not exist. This means we haven't started our update // check count down yet, so this is the right time to start the count down. update_check_count_value = - base::RandInt(params_->min_update_checks_needed(), - params_->max_update_checks_allowed()); + base::RandInt(params->min_update_checks_needed(), + params->max_update_checks_allowed()); LOG(INFO) << "Randomly picked update check count value = " << update_check_count_value; // Write out the initial value of update_check_count_value. - if (!system_state_->prefs()->SetInt64(kPrefsUpdateCheckCount, - update_check_count_value)) { + if (!SystemState::Get()->prefs()->SetInt64(kPrefsUpdateCheckCount, + update_check_count_value)) { // We weren't able to write the update check count file for some reason. // So let's proceed anyway so as to not stall the update. LOG(ERROR) << "Unable to write update check count. " @@ -1610,7 +1375,7 @@ bool OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied() { } if (update_check_count_value < 0 || - update_check_count_value > params_->max_update_checks_allowed()) { + update_check_count_value > params->max_update_checks_allowed()) { // We err on the side of skipping scattering logic instead of stalling // a machine from receiving any updates in case of any unexpected state. LOG(ERROR) << "Invalid value for update check count detected. " @@ -1629,7 +1394,7 @@ bool OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied() { bool OmahaRequestAction::ParseInstallDate(OmahaParserData* parser_data, OmahaResponse* output_object) { int64_t elapsed_days = 0; - if (!base::StringToInt64(parser_data->daystart_elapsed_days, &elapsed_days)) + if (!base::StringToInt64(parser_data->daystart.elapsed_days, &elapsed_days)) return false; if (elapsed_days < 0) @@ -1640,55 +1405,74 @@ bool OmahaRequestAction::ParseInstallDate(OmahaParserData* parser_data, } // static -bool OmahaRequestAction::HasInstallDate(SystemState* system_state) { - PrefsInterface* prefs = system_state->prefs(); - if (prefs == nullptr) - return false; - - return prefs->Exists(kPrefsInstallDateDays); +bool OmahaRequestAction::HasInstallDate() { + return SystemState::Get()->prefs()->Exists(kPrefsInstallDateDays); } // static bool OmahaRequestAction::PersistInstallDate( - SystemState* system_state, int install_date_days, InstallDateProvisioningSource source) { TEST_AND_RETURN_FALSE(install_date_days >= 0); - PrefsInterface* prefs = system_state->prefs(); - if (prefs == nullptr) - return false; - + auto* prefs = SystemState::Get()->prefs(); if (!prefs->SetInt64(kPrefsInstallDateDays, install_date_days)) return false; - system_state->metrics_reporter()->ReportInstallDateProvisioningSource( + SystemState::Get()->metrics_reporter()->ReportInstallDateProvisioningSource( static_cast<int>(source), // Sample. kProvisionedMax); // Maximum. return true; } -bool OmahaRequestAction::PersistCohortData(const string& prefs_key, - const string& new_value) { - if (new_value.empty() && system_state_->prefs()->Exists(prefs_key)) { - LOG(INFO) << "Removing stored " << prefs_key << " value."; - return system_state_->prefs()->Delete(prefs_key); - } else if (!new_value.empty()) { - LOG(INFO) << "Storing new setting " << prefs_key << " as " << new_value; - return system_state_->prefs()->SetString(prefs_key, new_value); +void OmahaRequestAction::PersistCohortData(const string& prefs_key, + const Optional<string>& new_value) { + if (!new_value) + return; + const string& value = new_value.value(); + if (value.empty() && SystemState::Get()->prefs()->Exists(prefs_key)) { + if (!SystemState::Get()->prefs()->Delete(prefs_key)) + LOG(ERROR) << "Failed to remove stored " << prefs_key << "value."; + else + LOG(INFO) << "Removed stored " << prefs_key << " value."; + } else if (!value.empty()) { + if (!SystemState::Get()->prefs()->SetString(prefs_key, value)) + LOG(INFO) << "Failed to store new setting " << prefs_key << " as " + << value; + else + LOG(INFO) << "Stored cohort setting " << prefs_key << " as " << value; } - return true; } -bool OmahaRequestAction::PersistEolStatus(const map<string, string>& attrs) { - auto eol_attr = attrs.find(kAttrEol); - if (eol_attr != attrs.end()) { - return system_state_->prefs()->SetString(kPrefsOmahaEolStatus, - eol_attr->second); - } else if (system_state_->prefs()->Exists(kPrefsOmahaEolStatus)) { - return system_state_->prefs()->Delete(kPrefsOmahaEolStatus); +void OmahaRequestAction::PersistCohorts(const OmahaParserData& parser_data) { + const auto* params = SystemState::Get()->request_params(); + for (const auto& app : parser_data.apps) { + // For platform App ID. + if (app.id == params->GetAppId()) { + PersistCohortData(kPrefsOmahaCohort, app.cohort); + PersistCohortData(kPrefsOmahaCohortName, app.cohortname); + PersistCohortData(kPrefsOmahaCohortHint, app.cohorthint); + } else if (params->IsDlcAppId(app.id)) { + string dlc_id; + if (!params->GetDlcId(app.id, &dlc_id)) { + LOG(WARNING) << "Skip persisting cohorts for DLC App ID=" << app.id + << " as it is not in the request params."; + continue; + } + auto* prefs = SystemState::Get()->prefs(); + PersistCohortData( + prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsOmahaCohort}), + app.cohort); + PersistCohortData( + prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsOmahaCohortName}), + app.cohortname); + PersistCohortData( + prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsOmahaCohortHint}), + app.cohorthint); + } else { + LOG(WARNING) << "Skip persisting cohorts for unknown App ID=" << app.id; + } } - return true; } void OmahaRequestAction::ActionCompleted(ErrorCode code) { @@ -1750,15 +1534,16 @@ void OmahaRequestAction::ActionCompleted(ErrorCode code) { break; } - system_state_->metrics_reporter()->ReportUpdateCheckMetrics( - system_state_, result, reaction, download_error_code); + SystemState::Get()->metrics_reporter()->ReportUpdateCheckMetrics( + result, reaction, download_error_code); } bool OmahaRequestAction::ShouldIgnoreUpdate(const OmahaResponse& response, ErrorCode* error) const { // Note: policy decision to not update to a version we rolled back from. string rollback_version = - system_state_->payload_state()->GetRollbackVersion(); + SystemState::Get()->payload_state()->GetRollbackVersion(); + const auto* params = SystemState::Get()->request_params(); if (!rollback_version.empty()) { LOG(INFO) << "Detected previous rollback from version " << rollback_version; if (rollback_version == response.version) { @@ -1768,11 +1553,11 @@ bool OmahaRequestAction::ShouldIgnoreUpdate(const OmahaResponse& response, } } - if (system_state_->hardware()->IsOOBEEnabled() && - !system_state_->hardware()->IsOOBEComplete(nullptr) && + if (SystemState::Get()->hardware()->IsOOBEEnabled() && + !SystemState::Get()->hardware()->IsOOBEComplete(nullptr) && (response.deadline.empty() || - system_state_->payload_state()->GetRollbackHappened()) && - params_->app_version() != "ForcedUpdate") { + SystemState::Get()->payload_state()->GetRollbackHappened()) && + params->app_version() != "ForcedUpdate") { LOG(INFO) << "Ignoring a non-critical Omaha update before OOBE completion."; *error = ErrorCode::kNonCriticalUpdateInOOBE; return true; @@ -1783,6 +1568,14 @@ bool OmahaRequestAction::ShouldIgnoreUpdate(const OmahaResponse& response, return true; } + // Currently non-critical updates always update alongside the platform update + // (a critical update) so this case should never actually be hit if the + // request to Omaha for updates are correct. In other words, stop the update + // from happening as there are no packages in the response to process. + if (response.packages.empty()) { + LOG(ERROR) << "All packages were excluded."; + } + // Note: We could technically delete the UpdateFirstSeenAt state when we // return true. If we do, it'll mean a device has to restart the // UpdateFirstSeenAt and thus help scattering take effect when the AU is @@ -1795,16 +1588,8 @@ bool OmahaRequestAction::ShouldIgnoreUpdate(const OmahaResponse& response, bool OmahaRequestAction::IsUpdateAllowedOverCellularByPrefs( const OmahaResponse& response) const { - PrefsInterface* prefs = system_state_->prefs(); - - if (!prefs) { - LOG(INFO) << "Disabling updates over cellular as the preferences are " - "not available."; - return false; - } - + auto* prefs = SystemState::Get()->prefs(); bool is_allowed; - if (prefs->Exists(kPrefsUpdateOverCellularPermission) && prefs->GetBoolean(kPrefsUpdateOverCellularPermission, &is_allowed) && is_allowed) { @@ -1852,7 +1637,7 @@ bool OmahaRequestAction::IsUpdateAllowedOverCurrentConnection( ConnectionType type; ConnectionTethering tethering; ConnectionManagerInterface* connection_manager = - system_state_->connection_manager(); + SystemState::Get()->connection_manager(); if (!connection_manager->GetConnectionProperties(&type, &tethering)) { LOG(INFO) << "We could not determine our connection type. " << "Defaulting to allow updates."; @@ -1915,7 +1700,8 @@ bool OmahaRequestAction::IsRollbackEnabled() const { void OmahaRequestAction::SetMaxKernelKeyVersionForRollback() const { int max_kernel_rollforward; - int min_kernel_version = system_state_->hardware()->GetMinKernelKeyVersion(); + int min_kernel_version = + SystemState::Get()->hardware()->GetMinKernelKeyVersion(); if (IsRollbackEnabled()) { // If rollback is enabled, set the max kernel key version to the current // kernel key version. This has the effect of freezing kernel key roll @@ -1941,22 +1727,22 @@ void OmahaRequestAction::SetMaxKernelKeyVersionForRollback() const { } bool max_rollforward_set = - system_state_->hardware()->SetMaxKernelKeyRollforward( + SystemState::Get()->hardware()->SetMaxKernelKeyRollforward( max_kernel_rollforward); if (!max_rollforward_set) { LOG(ERROR) << "Failed to set kernel_max_rollforward"; } // Report metrics - system_state_->metrics_reporter()->ReportKeyVersionMetrics( + SystemState::Get()->metrics_reporter()->ReportKeyVersionMetrics( min_kernel_version, max_kernel_rollforward, max_rollforward_set); } base::Time OmahaRequestAction::LoadOrPersistUpdateFirstSeenAtPref() const { Time update_first_seen_at; int64_t update_first_seen_at_int; - if (system_state_->prefs()->Exists(kPrefsUpdateFirstSeenAt)) { - if (system_state_->prefs()->GetInt64(kPrefsUpdateFirstSeenAt, - &update_first_seen_at_int)) { + if (SystemState::Get()->prefs()->Exists(kPrefsUpdateFirstSeenAt)) { + if (SystemState::Get()->prefs()->GetInt64(kPrefsUpdateFirstSeenAt, + &update_first_seen_at_int)) { // Note: This timestamp could be that of ANY update we saw in the past // (not necessarily this particular update we're considering to apply) // but never got to apply because of some reason (e.g. stop AU policy, @@ -1976,10 +1762,10 @@ base::Time OmahaRequestAction::LoadOrPersistUpdateFirstSeenAtPref() const { return base::Time(); } } else { - update_first_seen_at = system_state_->clock()->GetWallclockTime(); + update_first_seen_at = SystemState::Get()->clock()->GetWallclockTime(); update_first_seen_at_int = update_first_seen_at.ToInternalValue(); - if (system_state_->prefs()->SetInt64(kPrefsUpdateFirstSeenAt, - update_first_seen_at_int)) { + if (SystemState::Get()->prefs()->SetInt64(kPrefsUpdateFirstSeenAt, + update_first_seen_at_int)) { LOG(INFO) << "Persisted the new value for UpdateFirstSeenAt: " << utils::ToString(update_first_seen_at); } else { diff --git a/omaha_request_action.h b/cros/omaha_request_action.h index 8db5fb9b..4926c7d8 100644 --- a/omaha_request_action.h +++ b/cros/omaha_request_action.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_OMAHA_REQUEST_ACTION_H_ -#define UPDATE_ENGINE_OMAHA_REQUEST_ACTION_H_ +#ifndef UPDATE_ENGINE_CROS_OMAHA_REQUEST_ACTION_H_ +#define UPDATE_ENGINE_CROS_OMAHA_REQUEST_ACTION_H_ #include <fcntl.h> #include <sys/stat.h> @@ -28,13 +28,14 @@ #include <gtest/gtest_prod.h> // for FRIEND_TEST +#include <base/optional.h> #include <brillo/secure_blob.h> #include <curl/curl.h> #include "update_engine/common/action.h" #include "update_engine/common/http_fetcher.h" -#include "update_engine/omaha_response.h" -#include "update_engine/system_state.h" +#include "update_engine/cros/omaha_request_builder_xml.h" +#include "update_engine/cros/omaha_response.h" // The Omaha Request action makes a request to Omaha and can output // the response on the output ActionPipe. @@ -45,60 +46,9 @@ class PolicyProvider; namespace chromeos_update_engine { -// Encodes XML entities in a given string. Input must be ASCII-7 valid. If -// the input is invalid, the default value is used instead. -std::string XmlEncodeWithDefault(const std::string& input, - const std::string& default_value); - -// Escapes text so it can be included as character data and attribute -// values. The |input| string must be valid ASCII-7, no UTF-8 supported. -// Returns whether the |input| was valid and escaped properly in |output|. -bool XmlEncode(const std::string& input, std::string* output); - -// This struct encapsulates the Omaha event information. For a -// complete list of defined event types and results, see -// http://code.google.com/p/omaha/wiki/ServerProtocol#event -struct OmahaEvent { - // The Type values correspond to EVENT_TYPE values of Omaha. - enum Type { - kTypeUnknown = 0, - kTypeDownloadComplete = 1, - kTypeInstallComplete = 2, - kTypeUpdateComplete = 3, - kTypeUpdateDownloadStarted = 13, - kTypeUpdateDownloadFinished = 14, - // Chromium OS reserved type sent after the first reboot following an update - // completed. - kTypeRebootedAfterUpdate = 54, - }; - - // The Result values correspond to EVENT_RESULT values of Omaha. - enum Result { - kResultError = 0, - kResultSuccess = 1, - kResultUpdateDeferred = 9, // When we ignore/defer updates due to policy. - }; - - OmahaEvent() - : type(kTypeUnknown), - result(kResultError), - error_code(ErrorCode::kError) {} - explicit OmahaEvent(Type in_type) - : type(in_type), - result(kResultSuccess), - error_code(ErrorCode::kSuccess) {} - OmahaEvent(Type in_type, Result in_result, ErrorCode in_error_code) - : type(in_type), result(in_result), error_code(in_error_code) {} - - Type type; - Result result; - ErrorCode error_code; -}; - class NoneType; class OmahaRequestAction; class OmahaRequestParams; -class PrefsInterface; // This struct is declared in the .cc file. struct OmahaParserData; @@ -116,14 +66,13 @@ class ActionTraits<OmahaRequestAction> { class OmahaRequestAction : public Action<OmahaRequestAction>, public HttpFetcherDelegate { public: - static const int kNeverPinged = -1; static const int kPingTimeJump = -2; - // We choose this value of 10 as a heuristic for a work day in trying + // We choose this value of 3 as a heuristic for a work day in trying // each URL, assuming we check roughly every 45 mins. This is a good time to - // wait - neither too long nor too little - so we don't give up the preferred - // URLs that appear earlier in list too quickly before moving on to the - // fallback ones. - static const int kDefaultMaxFailureCountPerUrl = 10; + // wait so we don't give up the preferred URLs, but allow using the URL that + // appears earlier in list for every payload before resorting to the fallback + // URLs in the candiate URL list. + static const int kDefaultMaxFailureCountPerUrl = 3; // If staging is enabled, set the maximum wait time to 28 days, since that is // the predetermined wait time for staging. @@ -151,10 +100,10 @@ class OmahaRequestAction : public Action<OmahaRequestAction>, // OmahaRequestAction(..., new OmahaEvent(...), new WhateverHttpFetcher); // or // OmahaRequestAction(..., nullptr, new WhateverHttpFetcher); - OmahaRequestAction(SystemState* system_state, - OmahaEvent* event, + OmahaRequestAction(OmahaEvent* event, std::unique_ptr<HttpFetcher> http_fetcher, - bool ping_only); + bool ping_only, + const std::string& session_id); ~OmahaRequestAction() override; typedef ActionTraits<OmahaRequestAction>::InputObjectType InputObjectType; typedef ActionTraits<OmahaRequestAction>::OutputObjectType OutputObjectType; @@ -188,6 +137,9 @@ class OmahaRequestAction : public Action<OmahaRequestAction>, GetInstallDateWhenOOBECompletedWithValidDate); FRIEND_TEST(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedDateChanges); + friend class UpdateAttempterTest; + FRIEND_TEST(UpdateAttempterTest, SessionIdTestEnforceEmptyStrPingOmaha); + FRIEND_TEST(UpdateAttempterTest, SessionIdTestConsistencyInUpdateFlow); // Enumeration used in PersistInstallDate(). enum InstallDateProvisioningSource { @@ -202,7 +154,7 @@ class OmahaRequestAction : public Action<OmahaRequestAction>, // Gets the install date, expressed as the number of PST8PDT // calendar weeks since January 1st 2007, times seven. Returns -1 if // unknown. See http://crbug.com/336838 for details about this value. - static int GetInstallDate(SystemState* system_state); + static int GetInstallDate(); // Parses the Omaha Response in |doc| and sets the // |install_date_days| field of |output_object| to the value of the @@ -213,25 +165,25 @@ class OmahaRequestAction : public Action<OmahaRequestAction>, // Returns True if the kPrefsInstallDateDays state variable is set, // False otherwise. - static bool HasInstallDate(SystemState* system_state); + static bool HasInstallDate(); // Writes |install_date_days| into the kPrefsInstallDateDays state // variable and emits an UMA stat for the |source| used. Returns // True if the value was written, False if an error occurred. - static bool PersistInstallDate(SystemState* system_state, - int install_date_days, + static bool PersistInstallDate(int install_date_days, InstallDateProvisioningSource source); - // Persist the new cohort* value received in the XML file in the |prefs_key| - // preference file. If the |new_value| is empty, the currently stored value - // will be deleted. Don't call this function with an empty |new_value| if the - // value was not set in the XML, since that would delete the stored value. - bool PersistCohortData(const std::string& prefs_key, - const std::string& new_value); + // Persist the new cohort value received in the XML file in the |prefs_key| + // preference file. If the |new_value| is empty, do nothing. If the + // |new_value| stores and empty value, the currently stored value will be + // deleted. Don't call this function with an empty |new_value| if the value + // was not set in the XML, since that would delete the stored value. + void PersistCohortData(const std::string& prefs_key, + const base::Optional<std::string>& new_value); - // Parse and persist the end-of-life status flag sent back in the updatecheck - // tag attributes. The flag will be validated and stored in the Prefs. - bool PersistEolStatus(const std::map<std::string, std::string>& attrs); + // Parses and persists the cohorts sent back in the updatecheck tag + // attributes. + void PersistCohorts(const OmahaParserData& parser_data); // If this is an update check request, initializes // |ping_active_days_| and |ping_roll_call_days_| to values that may @@ -246,6 +198,10 @@ class OmahaRequestAction : public Action<OmahaRequestAction>, // send to Omaha and thus we should include them in the response. bool ShouldPing() const; + // Process Omaha's response to a ping request and store the results in the DLC + // metadata directory. + void StorePingReply(const OmahaParserData& parser_data) const; + // Returns true if the download of a new update should be deferred. // False if the update can be downloaded. bool ShouldDeferDownload(OmahaResponse* output_object); @@ -325,12 +281,6 @@ class OmahaRequestAction : public Action<OmahaRequestAction>, // kPrefsUpdateFirstSeenAt pref and returns it as a base::Time object. base::Time LoadOrPersistUpdateFirstSeenAtPref() const; - // Global system context. - SystemState* system_state_; - - // Contains state that is relevant in the processing of the Omaha request. - OmahaRequestParams* params_; - // Pointer to the OmahaEvent info. This is an UpdateCheck request if null. std::unique_ptr<OmahaEvent> event_; @@ -352,9 +302,11 @@ class OmahaRequestAction : public Action<OmahaRequestAction>, int ping_active_days_; int ping_roll_call_days_; + std::string session_id_; + DISALLOW_COPY_AND_ASSIGN(OmahaRequestAction); }; } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_OMAHA_REQUEST_ACTION_H_ +#endif // UPDATE_ENGINE_CROS_OMAHA_REQUEST_ACTION_H_ diff --git a/omaha_request_action_fuzzer.cc b/cros/omaha_request_action_fuzzer.cc index 6c2f7ca4..995de8c5 100644 --- a/omaha_request_action_fuzzer.cc +++ b/cros/omaha_request_action_fuzzer.cc @@ -18,8 +18,8 @@ #include "update_engine/common/mock_http_fetcher.h" #include "update_engine/common/test_utils.h" -#include "update_engine/fake_system_state.h" -#include "update_engine/omaha_request_action.h" +#include "update_engine/cros/fake_system_state.h" +#include "update_engine/cros/omaha_request_action.h" class Environment { public: @@ -31,14 +31,14 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { brillo::FakeMessageLoop loop(nullptr); loop.SetAsCurrent(); - chromeos_update_engine::FakeSystemState fake_system_state; + chromeos_update_engine::FakeSystemState::CreateInstance(); auto omaha_request_action = std::make_unique<chromeos_update_engine::OmahaRequestAction>( - &fake_system_state, nullptr, std::make_unique<chromeos_update_engine::MockHttpFetcher>( data, size, nullptr), - false); + false, + "" /* session_id */); auto collector_action = std::make_unique<chromeos_update_engine::ObjectCollectorAction< chromeos_update_engine::OmahaResponse>>(); diff --git a/cros/omaha_request_action_unittest.cc b/cros/omaha_request_action_unittest.cc new file mode 100644 index 00000000..01be1a82 --- /dev/null +++ b/cros/omaha_request_action_unittest.cc @@ -0,0 +1,3189 @@ +// +// Copyright (C) 2012 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/cros/omaha_request_action.h" + +#include <stdint.h> + +#include <limits> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include <base/bind.h> +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <base/memory/ptr_util.h> +#include <base/strings/string_number_conversions.h> +#include <base/strings/string_util.h> +#include <base/strings/stringprintf.h> +#include <base/time/time.h> +#include <brillo/message_loops/fake_message_loop.h> +#include <brillo/message_loops/message_loop.h> +#include <brillo/message_loops/message_loop_utils.h> +#include <expat.h> +#include <gtest/gtest.h> +#include <policy/libpolicy.h> +#include <policy/mock_libpolicy.h> + +#include "update_engine/common/action_pipe.h" +#include "update_engine/common/constants.h" +#include "update_engine/common/fake_prefs.h" +#include "update_engine/common/hash_calculator.h" +#include "update_engine/common/metrics_reporter_interface.h" +#include "update_engine/common/mock_excluder.h" +#include "update_engine/common/mock_http_fetcher.h" +#include "update_engine/common/platform_constants.h" +#include "update_engine/common/prefs.h" +#include "update_engine/common/test_utils.h" +#include "update_engine/cros/fake_system_state.h" +#include "update_engine/cros/mock_connection_manager.h" +#include "update_engine/cros/mock_payload_state.h" +#include "update_engine/cros/omaha_request_builder_xml.h" +#include "update_engine/cros/omaha_request_params.h" +#include "update_engine/cros/omaha_utils.h" +#include "update_engine/update_manager/rollback_prefs.h" + +using base::Time; +using base::TimeDelta; +using chromeos_update_manager::kRollforwardInfinity; +using std::pair; +using std::string; +using std::vector; +using testing::_; +using testing::AllOf; +using testing::AnyNumber; +using testing::DoAll; +using testing::Ge; +using testing::Le; +using testing::NiceMock; +using testing::Return; +using testing::ReturnPointee; +using testing::ReturnRef; +using testing::SaveArg; +using testing::SetArgPointee; +using testing::StrictMock; + +namespace { + +static_assert(kRollforwardInfinity == 0xfffffffe, + "Don't change the value of kRollforward infinity unless its " + "size has been changed in firmware."); + +const char kCurrentVersion[] = "0.1.0.0"; +const char kTestAppId[] = "test-app-id"; +const char kTestAppId2[] = "test-app2-id"; +const char kTestAppIdSkipUpdatecheck[] = "test-app-id-skip-updatecheck"; +const char kDlcId1[] = "dlc-id-1"; +const char kDlcId2[] = "dlc-id-2"; + +// This is a helper struct to allow unit tests build an update response with the +// values they care about. +struct FakeUpdateResponse { + string GetRollbackVersionAttributes() const { + string num_milestones; + num_milestones = base::NumberToString(rollback_allowed_milestones); + const string rollback_version = + " _firmware_version_" + num_milestones + "=\"" + + past_rollback_key_version.first + "\"" + " _kernel_version_" + + num_milestones + "=\"" + past_rollback_key_version.second + "\""; + + return (rollback ? " _rollback=\"true\"" : "") + rollback_version + + (!rollback_firmware_version.empty() + ? " _firmware_version=\"" + rollback_firmware_version + "\"" + : "") + + (!rollback_kernel_version.empty() + ? " _kernel_version=\"" + rollback_kernel_version + "\"" + : ""); + } + + string GetNoUpdateResponse() const { + string entity_str; + if (include_entity) + entity_str = "<!DOCTYPE response [<!ENTITY CrOS \"ChromeOS\">]>"; + return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + entity_str + + "<response protocol=\"3.0\">" + "<daystart elapsed_seconds=\"100\"/>" + "<app appid=\"" + + app_id + "\" " + + (include_cohorts + ? "cohort=\"" + cohort + "\" cohorthint=\"" + cohorthint + + "\" cohortname=\"" + cohortname + "\" " + : "") + + " status=\"ok\">" + "<ping status=\"ok\"/>" + "<updatecheck status=\"noupdate\"/></app>" + + (multi_app_no_update + ? "<app appid=\"" + app_id2 + + "\"><updatecheck status=\"noupdate\"/></app>" + : "") + + "</response>"; + } + + string GetUpdateResponse() const { + chromeos_update_engine::OmahaRequestParams request_params; + request_params.set_app_id(app_id); + return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " + "protocol=\"3.0\">" + "<daystart elapsed_seconds=\"100\"" + + (elapsed_days.empty() ? "" + : (" elapsed_days=\"" + elapsed_days + "\"")) + + "/>" + "<app appid=\"" + + app_id + "\" " + + (include_cohorts + ? "cohort=\"" + cohort + "\" cohorthint=\"" + cohorthint + + "\" cohortname=\"" + cohortname + "\" " + : "") + + " status=\"ok\">" + "<ping status=\"ok\"/><updatecheck status=\"ok\"" + + GetRollbackVersionAttributes() + ">" + "<urls><url codebase=\"" + + codebase + + "\"/></urls>" + "<manifest version=\"" + + version + + "\">" + "<packages><package hash=\"not-used\" name=\"" + + filename + "\" size=\"" + base::NumberToString(size) + "\" fp=\"" + + fp + "\" hash_sha256=\"" + hash + "\"/>" + + (multi_package ? "<package name=\"package2\" size=\"222\" fp=\"" + + fp2 + "\" hash_sha256=\"hash2\"/>" + : "") + + "</packages>" + "<actions><action event=\"postinstall\" MetadataSize=\"11" + + (multi_package ? ":22" : "") + "\" MoreInfo=\"" + more_info_url + + "\" Prompt=\"" + prompt + + "\" " + "IsDeltaPayload=\"true" + + (multi_package ? ":false" : "") + + "\" " + "MaxDaysToScatter=\"" + + max_days_to_scatter + + "\" " + "sha256=\"not-used\" " + + (deadline.empty() ? "" : ("deadline=\"" + deadline + "\" ")) + + (disable_p2p_for_downloading ? "DisableP2PForDownloading=\"true\" " + : "") + + (disable_p2p_for_sharing ? "DisableP2PForSharing=\"true\" " : "") + + (powerwash ? "Powerwash=\"true\" " : "") + + "/></actions></manifest></updatecheck></app>" + + (multi_app + ? "<app appid=\"" + app_id2 + "\"" + + (include_cohorts ? " cohort=\"cohort2\"" : "") + + "><updatecheck status=\"ok\"><urls><url codebase=\"" + + codebase2 + "\"/></urls><manifest version=\"" + version2 + + "\"><packages>" + "<package name=\"package3\" size=\"333\" fp=\"" + + fp2 + + "\" hash_sha256=\"hash3\"/></packages>" + "<actions><action event=\"postinstall\" " + + (multi_app_self_update + ? "noupdate=\"true\" IsDeltaPayload=\"true\" " + : "IsDeltaPayload=\"false\" ") + + "MetadataSize=\"33\"/></actions>" + "</manifest></updatecheck></app>" + : "") + + (multi_app_no_update + ? "<app><updatecheck status=\"noupdate\"/></app>" + : "") + + (multi_app_skip_updatecheck + ? "<app appid=\"" + app_id_skip_updatecheck + "\"></app>" + : "") + + (dlc_app_update + ? "<app appid=\"" + request_params.GetDlcAppId(kDlcId1) + + "\" " + + (include_dlc_cohorts + ? "cohort=\"" + dlc_cohort + "\" cohorthint=\"" + + dlc_cohorthint + "\" cohortname=\"" + + dlc_cohortname + "\" " + : "") + + "status=\"ok\">" + "<updatecheck status=\"ok\"><urls><url codebase=\"" + + codebase + "\"/><url codebase=\"" + codebase2 + + "\"/></urls><manifest version=\"" + version + + "\"><packages><package name=\"package3\" size=\"333\" " + "fp=\"" + + fp2 + + "\" hash_sha256=\"hash3\"/></packages>" + "<actions><action event=\"install\" run=\".signed\"/>" + "<action event=\"postinstall\" MetadataSize=\"33\"/>" + "</actions></manifest></updatecheck></app>" + : "") + + (dlc_app_no_update + ? "<app appid=\"" + request_params.GetDlcAppId(kDlcId2) + + +"\" " + + (include_dlc_cohorts + ? "cohort=\"" + dlc_cohort + "\" cohorthint=\"" + + dlc_cohorthint + "\" cohortname=\"" + + dlc_cohortname + "\" " + : "") + + "><updatecheck status=\"noupdate\"/></app>" + : "") + + "</response>"; + } + + // Return the payload URL, which is split in two fields in the XML response. + string GetPayloadUrl() { return codebase + filename; } + + string app_id = kTestAppId; + string app_id2 = kTestAppId2; + string app_id_skip_updatecheck = kTestAppIdSkipUpdatecheck; + string version = "1.2.3.4"; + string version2 = "2.3.4.5"; + string more_info_url = "http://more/info"; + string prompt = "true"; + string codebase = "http://code/base/"; + string codebase2 = "http://code/base/2/"; + string filename = "file.signed"; + string hash = "4841534831323334"; + string fp = "3.98ba213e"; + string fp2 = "3.755aff78e"; + uint64_t size = 123; + string deadline = ""; + string max_days_to_scatter = "7"; + string elapsed_days = "42"; + + // P2P setting defaults to allowed. + bool disable_p2p_for_downloading = false; + bool disable_p2p_for_sharing = false; + + bool powerwash = false; + + // Omaha cohorts settings. + bool include_cohorts = false; + string cohort = ""; + string cohorthint = ""; + string cohortname = ""; + // Whether to include Omaha cohorts for DLC apps. + bool include_dlc_cohorts = false; + string dlc_cohort = ""; + string dlc_cohorthint = ""; + string dlc_cohortname = ""; + + // Whether to include the CrOS <!ENTITY> in the XML response. + bool include_entity = false; + + // Whether to include more than one app. + bool multi_app = false; + // Whether to include an app with noupdate="true". + bool multi_app_self_update = false; + // Whether to include an additional app with status="noupdate". + bool multi_app_no_update = false; + // Whether to include an additional app with no updatecheck tag. + bool multi_app_skip_updatecheck = false; + // Whether to include more than one package in an app. + bool multi_package = false; + // Whether to include a DLC app with updatecheck tag. + bool dlc_app_update = false; + // Whether to include a DLC app with no updatecheck tag. + bool dlc_app_no_update = false; + + // Whether the payload is a rollback. + bool rollback = false; + // The verified boot firmware key version for the rollback image. + string rollback_firmware_version = ""; + // The verified boot kernel key version for the rollback image. + string rollback_kernel_version = ""; + // The number of milestones back that the verified boot key version has been + // supplied. + uint32_t rollback_allowed_milestones = 0; + // The verified boot key version for the + // |current - rollback_allowed_milestones| most recent release. + // The pair contains <firmware_key_version, kernel_key_version> each + // of which is in the form "key_version.version". + pair<string, string> past_rollback_key_version; +}; + +} // namespace + +namespace chromeos_update_engine { + +class OmahaRequestActionTestProcessorDelegate : public ActionProcessorDelegate { + public: + OmahaRequestActionTestProcessorDelegate() + : expected_code_(ErrorCode::kSuccess), + interactive_(false), + test_http_fetcher_headers_(false) {} + ~OmahaRequestActionTestProcessorDelegate() override = default; + + void ProcessingDone(const ActionProcessor* processor, + ErrorCode code) override { + brillo::MessageLoop::current()->BreakLoop(); + } + + void ActionCompleted(ActionProcessor* processor, + AbstractAction* action, + ErrorCode code) override { + // Make sure actions always succeed. + if (action->Type() == OmahaRequestAction::StaticType()) { + EXPECT_EQ(expected_code_, code); + // Check that the headers were set in the fetcher during the action. Note + // that we set this request as "interactive". + auto fetcher = static_cast<const MockHttpFetcher*>( + static_cast<OmahaRequestAction*>(action)->http_fetcher_.get()); + + if (test_http_fetcher_headers_) { + EXPECT_EQ(interactive_ ? "fg" : "bg", + fetcher->GetHeader("X-Goog-Update-Interactivity")); + EXPECT_EQ(kTestAppId, fetcher->GetHeader("X-Goog-Update-AppId")); + EXPECT_NE("", fetcher->GetHeader("X-Goog-Update-Updater")); + } + post_data_ = fetcher->post_data(); + } else if (action->Type() == + ObjectCollectorAction<OmahaResponse>::StaticType()) { + EXPECT_EQ(ErrorCode::kSuccess, code); + auto collector_action = + static_cast<ObjectCollectorAction<OmahaResponse>*>(action); + omaha_response_.reset(new OmahaResponse(collector_action->object())); + EXPECT_TRUE(omaha_response_); + } else { + EXPECT_EQ(ErrorCode::kSuccess, code); + } + } + ErrorCode expected_code_; + brillo::Blob post_data_; + bool interactive_; + bool test_http_fetcher_headers_; + std::unique_ptr<OmahaResponse> omaha_response_; +}; + +struct TestUpdateCheckParams { + string http_response; + int fail_http_response_code; + bool ping_only; + bool is_consumer_device; + int rollback_allowed_milestones; + bool is_policy_loaded; + ErrorCode expected_code; + metrics::CheckResult expected_check_result; + metrics::CheckReaction expected_check_reaction; + metrics::DownloadErrorCode expected_download_error_code; + string session_id; +}; + +class OmahaRequestActionTest : public ::testing::Test { + protected: + void SetUp() override { + FakeSystemState::CreateInstance(); + + request_params_.set_os_sp("service_pack"); + request_params_.set_os_board("x86-generic"); + request_params_.set_app_id(kTestAppId); + request_params_.set_app_version(kCurrentVersion); + request_params_.set_app_lang("en-US"); + request_params_.set_current_channel("unittest"); + request_params_.set_target_channel("unittest"); + request_params_.set_hwid("OEM MODEL 09235 7471"); + request_params_.set_delta_okay(true); + request_params_.set_interactive(false); + request_params_.set_update_url("http://url"); + request_params_.set_target_version_prefix(""); + request_params_.set_rollback_allowed(false); + request_params_.set_is_powerwash_allowed(false); + request_params_.set_is_install(false); + request_params_.set_dlc_apps_params({}); + + FakeSystemState::Get()->set_request_params(&request_params_); + fake_prefs_ = FakeSystemState::Get()->fake_prefs(); + + // Setting the default update check params. Lookup |TestUpdateCheck()|. + tuc_params_ = { + .http_response = "", + .fail_http_response_code = -1, + .ping_only = false, + .is_consumer_device = true, + .rollback_allowed_milestones = 0, + .is_policy_loaded = false, + .expected_code = ErrorCode::kSuccess, + .expected_check_result = metrics::CheckResult::kUpdateAvailable, + .expected_check_reaction = metrics::CheckReaction::kUpdating, + .expected_download_error_code = metrics::DownloadErrorCode::kUnset, + }; + + ON_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetExcluder()) + .WillByDefault(Return(&mock_excluder_)); + } + + // This function uses the parameters in |tuc_params_| to do an update check. + // It will fill out |post_str_| with the result data and |response| with + // |OmahaResponse|. Returns true iff an output response was obtained from the + // |OmahaRequestAction|. If |fail_http_response_code| is non-negative, the + // transfer will fail with that code. |ping_only| is passed through to the + // |OmahaRequestAction| constructor. + // + // The |expected_check_result|, |expected_check_reaction| and + // |expected_error_code| parameters are for checking expectations about + // reporting UpdateEngine.Check.{Result,Reaction,DownloadError} UMA + // statistics. Use the appropriate ::kUnset value to specify that the given + // metric should not be reported. + bool TestUpdateCheck(); + + // Tests events using |event| and |https_response|. It will fill up + // |post_str_| with the result data. + void TestEvent(OmahaEvent* event, const string& http_response); + + // Runs and checks a ping test. |ping_only| indicates whether it should send + // only a ping or also an updatecheck. + void PingTest(bool ping_only); + + // InstallDate test helper function. + bool InstallDateParseHelper(const string& elapsed_days, + OmahaResponse* response); + + // P2P test helper function. + void P2PTest(bool initial_allow_p2p_for_downloading, + bool initial_allow_p2p_for_sharing, + bool omaha_disable_p2p_for_downloading, + bool omaha_disable_p2p_for_sharing, + bool payload_state_allow_p2p_attempt, + bool expect_p2p_client_lookup, + const string& p2p_client_result_url, + bool expected_allow_p2p_for_downloading, + bool expected_allow_p2p_for_sharing, + const string& expected_p2p_url); + + StrictMock<MockExcluder> mock_excluder_; + FakeUpdateResponse fake_update_response_; + // Used by all tests. + OmahaRequestParams request_params_; + + FakePrefs* fake_prefs_; + + OmahaRequestActionTestProcessorDelegate delegate_; + + bool test_http_fetcher_headers_{false}; + + TestUpdateCheckParams tuc_params_; + + OmahaResponse response_; + string post_str_; +}; + +class OmahaRequestActionDlcPingTest : public OmahaRequestActionTest { + protected: + void SetUp() override { + OmahaRequestActionTest::SetUp(); + dlc_id_ = "dlc0"; + active_key_ = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, dlc_id_, kPrefsPingActive}); + last_active_key_ = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, dlc_id_, kPrefsPingLastActive}); + last_rollcall_key_ = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, dlc_id_, kPrefsPingLastRollcall}); + + tuc_params_.http_response = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " + "protocol=\"3.0\"><daystart elapsed_days=\"4763\" " + "elapsed_seconds=\"36540\"/><app appid=\"test-app-id\" status=\"ok\">\"" + "<updatecheck status=\"noupdate\"/></app><app " + "appid=\"test-app-id_dlc0\" " + "status=\"ok\"><ping status=\"ok\"/><updatecheck status=\"noupdate\"/>" + "</app></response>"; + tuc_params_.expected_check_result = + metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + } + + std::string dlc_id_; + std::string active_key_; + std::string last_active_key_; + std::string last_rollcall_key_; +}; + +bool OmahaRequestActionTest::TestUpdateCheck() { + brillo::FakeMessageLoop loop(nullptr); + loop.SetAsCurrent(); + auto fetcher = + std::make_unique<MockHttpFetcher>(tuc_params_.http_response.data(), + tuc_params_.http_response.size(), + nullptr); + if (tuc_params_.fail_http_response_code >= 0) { + fetcher->FailTransfer(tuc_params_.fail_http_response_code); + } + // This ensures the tests didn't forget to update |FakeSystemState| if they + // are not using the default |request_params_|. + EXPECT_EQ(&request_params_, FakeSystemState::Get()->request_params()); + + auto omaha_request_action = + std::make_unique<OmahaRequestAction>(nullptr, + std::move(fetcher), + tuc_params_.ping_only, + tuc_params_.session_id); + + auto mock_policy_provider = + std::make_unique<NiceMock<policy::MockPolicyProvider>>(); + EXPECT_CALL(*mock_policy_provider, IsConsumerDevice()) + .WillRepeatedly(Return(tuc_params_.is_consumer_device)); + + EXPECT_CALL(*mock_policy_provider, device_policy_is_loaded()) + .WillRepeatedly(Return(tuc_params_.is_policy_loaded)); + + const policy::MockDevicePolicy device_policy; + const bool get_allowed_milestone_succeeds = + tuc_params_.rollback_allowed_milestones >= 0; + EXPECT_CALL(device_policy, GetRollbackAllowedMilestones(_)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(tuc_params_.rollback_allowed_milestones), + Return(get_allowed_milestone_succeeds))); + + EXPECT_CALL(*mock_policy_provider, GetDevicePolicy()) + .WillRepeatedly(ReturnRef(device_policy)); + omaha_request_action->policy_provider_ = std::move(mock_policy_provider); + + delegate_.expected_code_ = tuc_params_.expected_code; + delegate_.interactive_ = request_params_.interactive(); + delegate_.test_http_fetcher_headers_ = test_http_fetcher_headers_; + ActionProcessor processor; + processor.set_delegate(&delegate_); + + auto collector_action = + std::make_unique<ObjectCollectorAction<OmahaResponse>>(); + BondActions(omaha_request_action.get(), collector_action.get()); + processor.EnqueueAction(std::move(omaha_request_action)); + processor.EnqueueAction(std::move(collector_action)); + + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), + ReportUpdateCheckMetrics(_, _, _)) + .Times(AnyNumber()); + + EXPECT_CALL( + *FakeSystemState::Get()->mock_metrics_reporter(), + ReportUpdateCheckMetrics(tuc_params_.expected_check_result, + tuc_params_.expected_check_reaction, + tuc_params_.expected_download_error_code)) + .Times(tuc_params_.ping_only ? 0 : 1); + + loop.PostTask(base::Bind( + [](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(&processor))); + loop.Run(); + EXPECT_FALSE(loop.PendingTasks()); + if (delegate_.omaha_response_) + response_ = *delegate_.omaha_response_; + post_str_ = string(delegate_.post_data_.begin(), delegate_.post_data_.end()); + return delegate_.omaha_response_ != nullptr; +} + +// Tests Event requests -- they should always succeed. |out_post_data| may be +// null; if non-null, the post-data received by the mock HttpFetcher is +// returned. +void OmahaRequestActionTest::TestEvent(OmahaEvent* event, + const string& http_response) { + brillo::FakeMessageLoop loop(nullptr); + loop.SetAsCurrent(); + + auto action = std::make_unique<OmahaRequestAction>( + event, + std::make_unique<MockHttpFetcher>( + http_response.data(), http_response.size(), nullptr), + false, + ""); + ActionProcessor processor; + processor.set_delegate(&delegate_); + processor.EnqueueAction(std::move(action)); + + loop.PostTask(base::Bind( + [](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(&processor))); + loop.Run(); + EXPECT_FALSE(loop.PendingTasks()); + + post_str_ = string(delegate_.post_data_.begin(), delegate_.post_data_.end()); +} + +TEST_F(OmahaRequestActionTest, RejectEntities) { + fake_update_response_.include_entity = true; + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLHasEntityDecl; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_FALSE(TestUpdateCheck()); + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, NoUpdateTest) { + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, MultiAppNoUpdateTest) { + fake_update_response_.multi_app_no_update = true; + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, MultiAppNoPartialUpdateTest) { + fake_update_response_.multi_app_no_update = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, NoSelfUpdateTest) { + tuc_params_.http_response = + "<response><app><updatecheck status=\"ok\"><manifest><actions><action " + "event=\"postinstall\" noupdate=\"true\"/></actions>" + "</manifest></updatecheck></app></response>"; + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_FALSE(response_.update_exists); +} + +// Test that all the values in the response are parsed in a normal update +// response_. +TEST_F(OmahaRequestActionTest, ValidUpdateTest) { + fake_update_response_.deadline = "20101020"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); + EXPECT_EQ(fake_update_response_.version, response_.version); + EXPECT_EQ(fake_update_response_.GetPayloadUrl(), + response_.packages[0].payload_urls[0]); + EXPECT_EQ(fake_update_response_.more_info_url, response_.more_info_url); + EXPECT_EQ(fake_update_response_.hash, response_.packages[0].hash); + EXPECT_EQ(fake_update_response_.size, response_.packages[0].size); + EXPECT_EQ(fake_update_response_.fp, response_.packages[0].fp); + EXPECT_EQ(true, response_.packages[0].is_delta); + EXPECT_EQ(fake_update_response_.prompt == "true", response_.prompt); + EXPECT_EQ(fake_update_response_.deadline, response_.deadline); + EXPECT_FALSE(response_.powerwash_required); + // Omaha cohort attributes are not set in the response, so they should not be + // persisted. + EXPECT_FALSE(fake_prefs_->Exists(kPrefsOmahaCohort)); + EXPECT_FALSE(fake_prefs_->Exists(kPrefsOmahaCohortHint)); + EXPECT_FALSE(fake_prefs_->Exists(kPrefsOmahaCohortName)); +} + +TEST_F(OmahaRequestActionTest, MultiPackageUpdateTest) { + fake_update_response_.multi_package = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); + EXPECT_EQ(fake_update_response_.version, response_.version); + EXPECT_EQ(fake_update_response_.GetPayloadUrl(), + response_.packages[0].payload_urls[0]); + EXPECT_EQ(fake_update_response_.codebase + "package2", + response_.packages[1].payload_urls[0]); + EXPECT_EQ(fake_update_response_.hash, response_.packages[0].hash); + EXPECT_EQ(fake_update_response_.size, response_.packages[0].size); + EXPECT_EQ(fake_update_response_.fp, response_.packages[0].fp); + EXPECT_EQ(true, response_.packages[0].is_delta); + EXPECT_EQ(11u, response_.packages[0].metadata_size); + ASSERT_EQ(2u, response_.packages.size()); + EXPECT_EQ(string("hash2"), response_.packages[1].hash); + EXPECT_EQ(222u, response_.packages[1].size); + EXPECT_EQ(fake_update_response_.fp2, response_.packages[1].fp); + EXPECT_EQ(22u, response_.packages[1].metadata_size); + EXPECT_EQ(false, response_.packages[1].is_delta); +} + +TEST_F(OmahaRequestActionTest, MultiAppUpdateTest) { + fake_update_response_.multi_app = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); + EXPECT_EQ(fake_update_response_.version, response_.version); + EXPECT_EQ(fake_update_response_.GetPayloadUrl(), + response_.packages[0].payload_urls[0]); + EXPECT_EQ(fake_update_response_.codebase2 + "package3", + response_.packages[1].payload_urls[0]); + EXPECT_EQ(fake_update_response_.hash, response_.packages[0].hash); + EXPECT_EQ(fake_update_response_.size, response_.packages[0].size); + EXPECT_EQ(fake_update_response_.fp, response_.packages[0].fp); + EXPECT_EQ(11u, response_.packages[0].metadata_size); + EXPECT_EQ(true, response_.packages[0].is_delta); + ASSERT_EQ(2u, response_.packages.size()); + EXPECT_EQ(string("hash3"), response_.packages[1].hash); + EXPECT_EQ(333u, response_.packages[1].size); + EXPECT_EQ(fake_update_response_.fp2, response_.packages[1].fp); + EXPECT_EQ(33u, response_.packages[1].metadata_size); + EXPECT_EQ(false, response_.packages[1].is_delta); +} + +TEST_F(OmahaRequestActionTest, MultiAppPartialUpdateTest) { + fake_update_response_.multi_app = true; + fake_update_response_.multi_app_self_update = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); + EXPECT_EQ(fake_update_response_.version, response_.version); + EXPECT_EQ(fake_update_response_.GetPayloadUrl(), + response_.packages[0].payload_urls[0]); + EXPECT_EQ(fake_update_response_.hash, response_.packages[0].hash); + EXPECT_EQ(fake_update_response_.size, response_.packages[0].size); + EXPECT_EQ(fake_update_response_.fp, response_.packages[0].fp); + EXPECT_EQ(11u, response_.packages[0].metadata_size); + ASSERT_EQ(2u, response_.packages.size()); + EXPECT_EQ(string("hash3"), response_.packages[1].hash); + EXPECT_EQ(333u, response_.packages[1].size); + EXPECT_EQ(fake_update_response_.fp2, response_.packages[1].fp); + EXPECT_EQ(33u, response_.packages[1].metadata_size); + EXPECT_EQ(true, response_.packages[1].is_delta); +} + +TEST_F(OmahaRequestActionTest, MultiAppMultiPackageUpdateTest) { + fake_update_response_.multi_app = true; + fake_update_response_.multi_package = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); + EXPECT_EQ(fake_update_response_.version, response_.version); + EXPECT_EQ(fake_update_response_.GetPayloadUrl(), + response_.packages[0].payload_urls[0]); + EXPECT_EQ(fake_update_response_.codebase + "package2", + response_.packages[1].payload_urls[0]); + EXPECT_EQ(fake_update_response_.codebase2 + "package3", + response_.packages[2].payload_urls[0]); + EXPECT_EQ(fake_update_response_.hash, response_.packages[0].hash); + EXPECT_EQ(fake_update_response_.size, response_.packages[0].size); + EXPECT_EQ(fake_update_response_.fp, response_.packages[0].fp); + EXPECT_EQ(11u, response_.packages[0].metadata_size); + EXPECT_EQ(true, response_.packages[0].is_delta); + ASSERT_EQ(3u, response_.packages.size()); + EXPECT_EQ(string("hash2"), response_.packages[1].hash); + EXPECT_EQ(222u, response_.packages[1].size); + EXPECT_EQ(fake_update_response_.fp2, response_.packages[1].fp); + EXPECT_EQ(22u, response_.packages[1].metadata_size); + EXPECT_EQ(false, response_.packages[1].is_delta); + EXPECT_EQ(string("hash3"), response_.packages[2].hash); + EXPECT_EQ(333u, response_.packages[2].size); + EXPECT_EQ(fake_update_response_.fp2, response_.packages[2].fp); + EXPECT_EQ(33u, response_.packages[2].metadata_size); + EXPECT_EQ(false, response_.packages[2].is_delta); +} + +TEST_F(OmahaRequestActionTest, PowerwashTest) { + fake_update_response_.powerwash = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); + EXPECT_TRUE(response_.powerwash_required); +} + +TEST_F(OmahaRequestActionTest, ExtraHeadersSentInteractiveTest) { + request_params_.set_interactive(true); + test_http_fetcher_headers_ = true; + tuc_params_.http_response = "invalid xml>"; + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, ExtraHeadersSentNoInteractiveTest) { + request_params_.set_interactive(false); + test_http_fetcher_headers_ = true; + tuc_params_.http_response = "invalid xml>"; + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByConnection) { + // Set up a connection manager that doesn't allow a valid update over + // the current ethernet connection. + MockConnectionManager mock_cm; + FakeSystemState::Get()->set_connection_manager(&mock_cm); + + EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kEthernet), + SetArgPointee<1>(ConnectionTethering::kUnknown), + Return(true))); + EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kEthernet, _)) + .WillRepeatedly(Return(false)); + + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kOmahaUpdateIgnoredPerPolicy; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kIgnored; + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, ValidUpdateOverCellularAllowedByDevicePolicy) { + // This test tests that update over cellular is allowed as device policy + // says yes. + MockConnectionManager mock_cm; + FakeSystemState::Get()->set_connection_manager(&mock_cm); + + EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular), + SetArgPointee<1>(ConnectionTethering::kUnknown), + Return(true))); + EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet()) + .WillRepeatedly(Return(true)); + EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _)) + .WillRepeatedly(Return(true)); + + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, ValidUpdateOverCellularBlockedByDevicePolicy) { + // This test tests that update over cellular is blocked as device policy + // says no. + MockConnectionManager mock_cm; + FakeSystemState::Get()->set_connection_manager(&mock_cm); + + EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular), + SetArgPointee<1>(ConnectionTethering::kUnknown), + Return(true))); + EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet()) + .WillRepeatedly(Return(true)); + EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _)) + .WillRepeatedly(Return(false)); + + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kOmahaUpdateIgnoredPerPolicy; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kIgnored; + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, + ValidUpdateOverCellularAllowedByUserPermissionTrue) { + // This test tests that, when device policy is not set, update over cellular + // is allowed as permission for update over cellular is set to true. + MockConnectionManager mock_cm; + fake_prefs_->SetBoolean(kPrefsUpdateOverCellularPermission, true); + FakeSystemState::Get()->set_connection_manager(&mock_cm); + + EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular), + SetArgPointee<1>(ConnectionTethering::kUnknown), + Return(true))); + EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet()) + .WillRepeatedly(Return(false)); + EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _)) + .WillRepeatedly(Return(true)); + + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, + ValidUpdateOverCellularBlockedByUpdateTargetNotMatch) { + // This test tests that, when device policy is not set and permission for + // update over cellular is set to false or does not exist, update over + // cellular is blocked as update target does not match the omaha response. + MockConnectionManager mock_cm; + // A version different from the version in omaha response. + string diff_version = "99.99.99"; + // A size different from the size in omaha response. + int64_t diff_size = 999; + + fake_prefs_->SetString(kPrefsUpdateOverCellularTargetVersion, diff_version); + fake_prefs_->SetInt64(kPrefsUpdateOverCellularTargetSize, diff_size); + // This test tests cellular (3G) being the only connection type being allowed. + FakeSystemState::Get()->set_connection_manager(&mock_cm); + + EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular), + SetArgPointee<1>(ConnectionTethering::kUnknown), + Return(true))); + EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet()) + .WillRepeatedly(Return(false)); + EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _)) + .WillRepeatedly(Return(true)); + + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kOmahaUpdateIgnoredOverCellular; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kIgnored; + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, + ValidUpdateOverCellularAllowedByUpdateTargetMatch) { + // This test tests that, when device policy is not set and permission for + // update over cellular is set to false or does not exist, update over + // cellular is allowed as update target matches the omaha response. + MockConnectionManager mock_cm; + // A version same as the version in omaha response. + string new_version = fake_update_response_.version; + // A size same as the size in omaha response. + int64_t new_size = fake_update_response_.size; + + fake_prefs_->SetString(kPrefsUpdateOverCellularTargetVersion, new_version); + fake_prefs_->SetInt64(kPrefsUpdateOverCellularTargetSize, new_size); + FakeSystemState::Get()->set_connection_manager(&mock_cm); + + EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular), + SetArgPointee<1>(ConnectionTethering::kUnknown), + Return(true))); + EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet()) + .WillRepeatedly(Return(false)); + EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _)) + .WillRepeatedly(Return(true)); + + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByRollback) { + string rollback_version = "1234.0.0"; + MockPayloadState mock_payload_state; + FakeSystemState::Get()->set_payload_state(&mock_payload_state); + fake_update_response_.version = rollback_version; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kOmahaUpdateIgnoredPerPolicy; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kIgnored; + + EXPECT_CALL(mock_payload_state, GetRollbackVersion()) + .WillRepeatedly(Return(rollback_version)); + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); +} + +// Verify that update checks called during OOBE will not try to download an +// update if the response doesn't include the deadline field. +TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBE) { + FakeSystemState::Get()->fake_hardware()->UnsetIsOOBEComplete(); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kNonCriticalUpdateInOOBE; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + // TODO(senj): set better default value for metrics::checkresult in + // OmahaRequestAction::ActionCompleted. + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); +} + +// Verify that the IsOOBEComplete() value is ignored when the OOBE flow is not +// enabled. +TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBEDisabled) { + FakeSystemState::Get()->fake_hardware()->UnsetIsOOBEComplete(); + FakeSystemState::Get()->fake_hardware()->SetIsOOBEEnabled(false); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); +} + +// Verify that update checks called during OOBE will still try to download an +// update if the response includes the deadline field. +TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBEDeadlineSet) { + FakeSystemState::Get()->fake_hardware()->UnsetIsOOBEComplete(); + fake_update_response_.deadline = "20101020"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); +} + +// Verify that update checks called during OOBE will not try to download an +// update if a rollback happened, even when the response includes the deadline +// field. +TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBERollback) { + FakeSystemState::Get()->fake_hardware()->UnsetIsOOBEComplete(); + fake_update_response_.deadline = "20101020"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kNonCriticalUpdateInOOBE; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + EXPECT_CALL(*(FakeSystemState::Get()->mock_payload_state()), + GetRollbackHappened()) + .WillOnce(Return(true)); + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); +} + +// Verify that non-critical updates are skipped by reporting the +// kNonCriticalUpdateInOOBE error code when attempted over cellular network - +// i.e. when the update would need user permission. Note that reporting +// kOmahaUpdateIgnoredOverCellular error in this case might cause undesired UX +// in OOBE (warning the user about an update that will be skipped). +TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesInOOBEOverCellular) { + FakeSystemState::Get()->fake_hardware()->UnsetIsOOBEComplete(); + + MockConnectionManager mock_cm; + FakeSystemState::Get()->set_connection_manager(&mock_cm); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kNonCriticalUpdateInOOBE; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) + .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular), + SetArgPointee<1>(ConnectionTethering::kUnknown), + Return(true))); + EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet()) + .WillRepeatedly(Return(false)); + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, WallClockBasedWaitAloneCausesScattering) { + request_params_.set_wall_clock_based_wait_enabled(true); + request_params_.set_update_check_count_wait_enabled(false); + request_params_.set_waiting_period(TimeDelta::FromDays(2)); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(Time::Now()); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kOmahaUpdateDeferredPerPolicy; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kDeferring; + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, + WallClockBasedWaitAloneCausesScatteringInteractive) { + request_params_.set_wall_clock_based_wait_enabled(true); + request_params_.set_update_check_count_wait_enabled(false); + request_params_.set_waiting_period(TimeDelta::FromDays(2)); + request_params_.set_interactive(true); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(Time::Now()); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + // Verify if we are interactive check we don't defer. + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, NoWallClockBasedWaitCausesNoScattering) { + request_params_.set_wall_clock_based_wait_enabled(false); + request_params_.set_waiting_period(TimeDelta::FromDays(2)); + request_params_.set_update_check_count_wait_enabled(true); + request_params_.set_min_update_checks_needed(1); + request_params_.set_max_update_checks_allowed(8); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, ZeroMaxDaysToScatterCausesNoScattering) { + request_params_.set_wall_clock_based_wait_enabled(true); + request_params_.set_waiting_period(TimeDelta::FromDays(2)); + request_params_.set_update_check_count_wait_enabled(true); + request_params_.set_min_update_checks_needed(1); + request_params_.set_max_update_checks_allowed(8); + fake_update_response_.max_days_to_scatter = "0"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, ZeroUpdateCheckCountCausesNoScattering) { + request_params_.set_wall_clock_based_wait_enabled(true); + request_params_.set_waiting_period(TimeDelta()); + request_params_.set_update_check_count_wait_enabled(true); + request_params_.set_min_update_checks_needed(0); + request_params_.set_max_update_checks_allowed(0); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(Time::Now()); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + int64_t count; + ASSERT_TRUE(fake_prefs_->GetInt64(kPrefsUpdateCheckCount, &count)); + ASSERT_EQ(count, 0); + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, NonZeroUpdateCheckCountCausesScattering) { + request_params_.set_wall_clock_based_wait_enabled(true); + request_params_.set_waiting_period(TimeDelta()); + request_params_.set_update_check_count_wait_enabled(true); + request_params_.set_min_update_checks_needed(1); + request_params_.set_max_update_checks_allowed(8); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(Time::Now()); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kOmahaUpdateDeferredPerPolicy; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kDeferring; + + ASSERT_FALSE(TestUpdateCheck()); + + int64_t count; + ASSERT_TRUE(fake_prefs_->GetInt64(kPrefsUpdateCheckCount, &count)); + ASSERT_GT(count, 0); + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, + NonZeroUpdateCheckCountCausesScatteringInteractive) { + request_params_.set_wall_clock_based_wait_enabled(true); + request_params_.set_waiting_period(TimeDelta()); + request_params_.set_update_check_count_wait_enabled(true); + request_params_.set_min_update_checks_needed(1); + request_params_.set_max_update_checks_allowed(8); + request_params_.set_interactive(true); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(Time::Now()); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + // Verify if we are interactive check we don't defer. + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, ExistingUpdateCheckCountCausesScattering) { + request_params_.set_wall_clock_based_wait_enabled(true); + request_params_.set_waiting_period(TimeDelta()); + request_params_.set_update_check_count_wait_enabled(true); + request_params_.set_min_update_checks_needed(1); + request_params_.set_max_update_checks_allowed(8); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(Time::Now()); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kOmahaUpdateDeferredPerPolicy; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kDeferring; + + ASSERT_TRUE(fake_prefs_->SetInt64(kPrefsUpdateCheckCount, 5)); + ASSERT_FALSE(TestUpdateCheck()); + + int64_t count; + ASSERT_TRUE(fake_prefs_->GetInt64(kPrefsUpdateCheckCount, &count)); + // |count| remains the same, as the decrementing happens in update_attempter + // which this test doesn't exercise. + ASSERT_EQ(count, 5); + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, + ExistingUpdateCheckCountCausesScatteringInteractive) { + request_params_.set_wall_clock_based_wait_enabled(true); + request_params_.set_waiting_period(TimeDelta()); + request_params_.set_update_check_count_wait_enabled(true); + request_params_.set_min_update_checks_needed(1); + request_params_.set_max_update_checks_allowed(8); + request_params_.set_interactive(true); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(Time::Now()); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(fake_prefs_->SetInt64(kPrefsUpdateCheckCount, 5)); + + // Verify if we are interactive check we don't defer. + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, StagingTurnedOnCausesScattering) { + // If staging is on, the value for max days to scatter should be ignored, and + // staging's scatter value should be used. + request_params_.set_wall_clock_based_wait_enabled(true); + request_params_.set_waiting_period(TimeDelta::FromDays(6)); + request_params_.set_update_check_count_wait_enabled(false); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(Time::Now()); + + ASSERT_TRUE(fake_prefs_->SetInt64(kPrefsWallClockStagingWaitPeriod, 6)); + // This should not prevent scattering due to staging. + fake_update_response_.max_days_to_scatter = "0"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kOmahaUpdateDeferredPerPolicy; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kDeferring; + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); + + // Interactive updates should not be affected. + request_params_.set_interactive(true); + tuc_params_.expected_code = ErrorCode::kSuccess; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUpdating; + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, CohortsArePersisted) { + fake_update_response_.include_cohorts = true; + fake_update_response_.cohort = "s/154454/8479665"; + fake_update_response_.cohorthint = "please-put-me-on-beta"; + fake_update_response_.cohortname = "stable"; + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}}); + fake_update_response_.dlc_app_update = true; + fake_update_response_.include_dlc_cohorts = true; + fake_update_response_.dlc_cohort = "s/154454/8479665/dlc"; + fake_update_response_.dlc_cohorthint = "please-put-me-on-beta-dlc"; + fake_update_response_.dlc_cohortname = "stable-dlc"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false)); + ASSERT_TRUE(TestUpdateCheck()); + + string value; + EXPECT_TRUE(fake_prefs_->GetString(kPrefsOmahaCohort, &value)); + EXPECT_EQ(fake_update_response_.cohort, value); + + EXPECT_TRUE(fake_prefs_->GetString(kPrefsOmahaCohortHint, &value)); + EXPECT_EQ(fake_update_response_.cohorthint, value); + + EXPECT_TRUE(fake_prefs_->GetString(kPrefsOmahaCohortName, &value)); + EXPECT_EQ(fake_update_response_.cohortname, value); + + EXPECT_TRUE(fake_prefs_->GetString( + fake_prefs_->CreateSubKey({kDlcPrefsSubDir, kDlcId1, kPrefsOmahaCohort}), + &value)); + EXPECT_EQ(fake_update_response_.dlc_cohort, value); + + EXPECT_TRUE(fake_prefs_->GetString( + fake_prefs_->CreateSubKey( + {kDlcPrefsSubDir, kDlcId1, kPrefsOmahaCohortHint}), + &value)); + EXPECT_EQ(fake_update_response_.dlc_cohorthint, value); + + EXPECT_TRUE(fake_prefs_->GetString( + fake_prefs_->CreateSubKey( + {kDlcPrefsSubDir, kDlcId1, kPrefsOmahaCohortName}), + &value)); + EXPECT_EQ(fake_update_response_.dlc_cohortname, value); +} + +TEST_F(OmahaRequestActionTest, CohortsAreUpdated) { + EXPECT_TRUE(fake_prefs_->SetString(kPrefsOmahaCohort, "old_value")); + EXPECT_TRUE(fake_prefs_->SetString(kPrefsOmahaCohortHint, "old_hint")); + EXPECT_TRUE(fake_prefs_->SetString(kPrefsOmahaCohortName, "old_name")); + const string dlc_cohort_key = + fake_prefs_->CreateSubKey({kDlcPrefsSubDir, kDlcId1, kPrefsOmahaCohort}); + const string dlc_cohort_hint_key = fake_prefs_->CreateSubKey( + {kDlcPrefsSubDir, kDlcId1, kPrefsOmahaCohortHint}); + const string dlc_cohort_name_key = fake_prefs_->CreateSubKey( + {kDlcPrefsSubDir, kDlcId1, kPrefsOmahaCohortName}); + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}}); + EXPECT_TRUE(fake_prefs_->SetString(dlc_cohort_key, "old_value_dlc")); + EXPECT_TRUE(fake_prefs_->SetString(dlc_cohort_hint_key, "old_hint_dlc")); + EXPECT_TRUE(fake_prefs_->SetString(dlc_cohort_name_key, "old_name_dlc")); + fake_update_response_.include_cohorts = true; + fake_update_response_.cohort = "s/154454/8479665"; + fake_update_response_.cohorthint = "please-put-me-on-beta"; + fake_update_response_.cohortname = ""; + fake_update_response_.dlc_app_update = true; + fake_update_response_.include_dlc_cohorts = true; + fake_update_response_.dlc_cohort = "s/154454/8479665/dlc"; + fake_update_response_.dlc_cohorthint = "please-put-me-on-beta-dlc"; + fake_update_response_.dlc_cohortname = ""; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false)); + ASSERT_TRUE(TestUpdateCheck()); + + string value; + EXPECT_TRUE(fake_prefs_->GetString(kPrefsOmahaCohort, &value)); + EXPECT_EQ(fake_update_response_.cohort, value); + + EXPECT_TRUE(fake_prefs_->GetString(kPrefsOmahaCohortHint, &value)); + EXPECT_EQ(fake_update_response_.cohorthint, value); + + EXPECT_FALSE(fake_prefs_->GetString(kPrefsOmahaCohortName, &value)); + + EXPECT_TRUE(fake_prefs_->GetString(dlc_cohort_key, &value)); + EXPECT_EQ(fake_update_response_.dlc_cohort, value); + + EXPECT_TRUE(fake_prefs_->GetString(dlc_cohort_hint_key, &value)); + EXPECT_EQ(fake_update_response_.dlc_cohorthint, value); + + EXPECT_FALSE(fake_prefs_->GetString(dlc_cohort_name_key, &value)); +} + +TEST_F(OmahaRequestActionTest, CohortsAreNotModifiedWhenMissing) { + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + EXPECT_TRUE(fake_prefs_->SetString(kPrefsOmahaCohort, "old_value")); + const string dlc_cohort_key = + fake_prefs_->CreateSubKey({kDlcPrefsSubDir, kDlcId1, kPrefsOmahaCohort}); + EXPECT_TRUE(fake_prefs_->SetString(dlc_cohort_key, "old_value_dlc")); + ASSERT_TRUE(TestUpdateCheck()); + + string value; + EXPECT_TRUE(fake_prefs_->GetString(kPrefsOmahaCohort, &value)); + EXPECT_EQ("old_value", value); + + EXPECT_FALSE(fake_prefs_->GetString(kPrefsOmahaCohortHint, &value)); + EXPECT_FALSE(fake_prefs_->GetString(kPrefsOmahaCohortName, &value)); + + EXPECT_TRUE(fake_prefs_->GetString(dlc_cohort_key, &value)); + EXPECT_EQ("old_value_dlc", value); + + EXPECT_FALSE(fake_prefs_->GetString( + fake_prefs_->CreateSubKey( + {kDlcPrefsSubDir, kDlcId1, kPrefsOmahaCohortHint}), + &value)); + EXPECT_FALSE(fake_prefs_->GetString( + fake_prefs_->CreateSubKey( + {kDlcPrefsSubDir, kDlcId1, kPrefsOmahaCohortName}), + &value)); +} + +TEST_F(OmahaRequestActionTest, CohortsArePersistedWhenNoUpdate) { + fake_update_response_.include_cohorts = true; + fake_update_response_.cohort = "s/154454/8479665"; + fake_update_response_.cohorthint = "please-put-me-on-beta"; + fake_update_response_.cohortname = "stable"; + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + + string value; + EXPECT_TRUE(fake_prefs_->GetString(kPrefsOmahaCohort, &value)); + EXPECT_EQ(fake_update_response_.cohort, value); + + EXPECT_TRUE(fake_prefs_->GetString(kPrefsOmahaCohortHint, &value)); + EXPECT_EQ(fake_update_response_.cohorthint, value); + + EXPECT_TRUE(fake_prefs_->GetString(kPrefsOmahaCohortName, &value)); + EXPECT_EQ(fake_update_response_.cohortname, value); +} + +TEST_F(OmahaRequestActionTest, MultiAppCohortTest) { + fake_update_response_.multi_app = true; + fake_update_response_.include_cohorts = true; + fake_update_response_.cohort = "s/154454/8479665"; + fake_update_response_.cohorthint = "please-put-me-on-beta"; + fake_update_response_.cohortname = "stable"; + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}, + {request_params_.GetDlcAppId(kDlcId2), {.name = kDlcId2}}}); + fake_update_response_.dlc_app_update = true; + fake_update_response_.dlc_app_no_update = true; + fake_update_response_.include_dlc_cohorts = true; + fake_update_response_.dlc_cohort = "s/154454/8479665/dlc"; + fake_update_response_.dlc_cohorthint = "please-put-me-on-beta-dlc"; + fake_update_response_.dlc_cohortname = "stable-dlc"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false)); + ASSERT_TRUE(TestUpdateCheck()); + + string value; + EXPECT_TRUE(fake_prefs_->GetString(kPrefsOmahaCohort, &value)); + EXPECT_EQ(fake_update_response_.cohort, value); + + EXPECT_TRUE(fake_prefs_->GetString(kPrefsOmahaCohortHint, &value)); + EXPECT_EQ(fake_update_response_.cohorthint, value); + + EXPECT_TRUE(fake_prefs_->GetString(kPrefsOmahaCohortName, &value)); + EXPECT_EQ(fake_update_response_.cohortname, value); + + EXPECT_TRUE(fake_prefs_->GetString( + fake_prefs_->CreateSubKey({kDlcPrefsSubDir, kDlcId1, kPrefsOmahaCohort}), + &value)); + EXPECT_EQ(fake_update_response_.dlc_cohort, value); + EXPECT_TRUE(fake_prefs_->GetString( + fake_prefs_->CreateSubKey({kDlcPrefsSubDir, kDlcId2, kPrefsOmahaCohort}), + &value)); + EXPECT_EQ(fake_update_response_.dlc_cohort, value); + + EXPECT_TRUE(fake_prefs_->GetString( + fake_prefs_->CreateSubKey( + {kDlcPrefsSubDir, kDlcId1, kPrefsOmahaCohortHint}), + &value)); + EXPECT_EQ(fake_update_response_.dlc_cohorthint, value); + EXPECT_TRUE(fake_prefs_->GetString( + fake_prefs_->CreateSubKey( + {kDlcPrefsSubDir, kDlcId2, kPrefsOmahaCohortHint}), + &value)); + EXPECT_EQ(fake_update_response_.dlc_cohorthint, value); + + EXPECT_TRUE(fake_prefs_->GetString( + fake_prefs_->CreateSubKey( + {kDlcPrefsSubDir, kDlcId1, kPrefsOmahaCohortName}), + &value)); + EXPECT_EQ(fake_update_response_.dlc_cohortname, value); + EXPECT_TRUE(fake_prefs_->GetString( + fake_prefs_->CreateSubKey( + {kDlcPrefsSubDir, kDlcId2, kPrefsOmahaCohortName}), + &value)); + EXPECT_EQ(fake_update_response_.dlc_cohortname, value); +} + +TEST_F(OmahaRequestActionTest, NoOutputPipeTest) { + const string http_response(fake_update_response_.GetNoUpdateResponse()); + brillo::FakeMessageLoop loop(nullptr); + loop.SetAsCurrent(); + + auto action = std::make_unique<OmahaRequestAction>( + nullptr, + std::make_unique<MockHttpFetcher>( + http_response.data(), http_response.size(), nullptr), + false, + ""); + ActionProcessor processor; + processor.set_delegate(&delegate_); + processor.EnqueueAction(std::move(action)); + + loop.PostTask(base::Bind( + [](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(&processor))); + loop.Run(); + EXPECT_FALSE(loop.PendingTasks()); + EXPECT_FALSE(processor.IsRunning()); +} + +TEST_F(OmahaRequestActionTest, InvalidXmlTest) { + tuc_params_.http_response = "invalid xml>"; + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_FALSE(TestUpdateCheck()); + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, EmptyResponseTest) { + tuc_params_.expected_code = ErrorCode::kOmahaRequestEmptyResponseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_FALSE(TestUpdateCheck()); + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, MissingStatusTest) { + tuc_params_.http_response = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">" + "<daystart elapsed_seconds=\"100\"/>" + "<app appid=\"foo\" status=\"ok\">" + "<ping status=\"ok\"/>" + "<updatecheck/></app></response>"; + tuc_params_.expected_code = ErrorCode::kOmahaResponseInvalid; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_FALSE(TestUpdateCheck()); + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, InvalidStatusTest) { + tuc_params_.http_response = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">" + "<daystart elapsed_seconds=\"100\"/>" + "<app appid=\"foo\" status=\"ok\">" + "<ping status=\"ok\"/>" + "<updatecheck status=\"InvalidStatusTest\"/></app></response>"; + tuc_params_.expected_code = ErrorCode::kOmahaResponseInvalid; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_FALSE(TestUpdateCheck()); + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, MissingNodesetTest) { + tuc_params_.http_response = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">" + "<daystart elapsed_seconds=\"100\"/>" + "<app appid=\"foo\" status=\"ok\">" + "<ping status=\"ok\"/>" + "</app></response>"; + tuc_params_.expected_code = ErrorCode::kOmahaResponseInvalid; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_FALSE(TestUpdateCheck()); + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, MissingFieldTest) { + tuc_params_.http_response = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">" + "<daystart elapsed_seconds=\"100\"/>" + // the appid needs to match that in the request params + "<app appid=\"" + + fake_update_response_.app_id + + "\" status=\"ok\">" + "<updatecheck status=\"ok\">" + "<urls><url codebase=\"http://missing/field/test/\"/></urls>" + "<manifest version=\"10.2.3.4\">" + "<packages><package hash=\"not-used\" name=\"f\" " + "size=\"587\" fp=\"3.789\" hash_sha256=\"lkq34j5345\"/></packages>" + "<actions><action event=\"postinstall\" " + "Prompt=\"false\" " + "IsDeltaPayload=\"false\" " + "sha256=\"not-used\" " + "/></actions></manifest></updatecheck></app></response>"; + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); + EXPECT_EQ("10.2.3.4", response_.version); + EXPECT_EQ("http://missing/field/test/f", + response_.packages[0].payload_urls[0]); + EXPECT_EQ("", response_.more_info_url); + EXPECT_EQ("lkq34j5345", response_.packages[0].hash); + EXPECT_EQ(string("3.789"), response_.packages[0].fp); + EXPECT_EQ(587u, response_.packages[0].size); + EXPECT_FALSE(response_.prompt); + EXPECT_TRUE(response_.deadline.empty()); +} + +namespace { +class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate { + public: + void ProcessingStopped(const ActionProcessor* processor) { + brillo::MessageLoop::current()->BreakLoop(); + } +}; + +void TerminateTransferTestStarter(ActionProcessor* processor) { + processor->StartProcessing(); + CHECK(processor->IsRunning()); + processor->StopProcessing(); +} +} // namespace + +TEST_F(OmahaRequestActionTest, TerminateTransferTest) { + brillo::FakeMessageLoop loop(nullptr); + loop.SetAsCurrent(); + + string http_response("doesn't matter"); + auto action = std::make_unique<OmahaRequestAction>( + nullptr, + std::make_unique<MockHttpFetcher>( + http_response.data(), http_response.size(), nullptr), + false, + ""); + TerminateEarlyTestProcessorDelegate delegate; + ActionProcessor processor; + processor.set_delegate(&delegate); + processor.EnqueueAction(std::move(action)); + + loop.PostTask(base::Bind(&TerminateTransferTestStarter, &processor)); + loop.Run(); + EXPECT_FALSE(loop.PendingTasks()); +} + +TEST_F(OmahaRequestActionTest, XmlEncodeIsUsedForParams) { + // Make sure XML Encode is being called on the params. + request_params_.set_os_sp("testtheservice_pack>"); + request_params_.set_os_board("x86 generic<id"); + request_params_.set_current_channel("unittest_track<"); + request_params_.set_target_channel("unittest_track<"); + request_params_.set_lts_tag("unittest_hint<"); + request_params_.set_hwid("<OEM MODEL>"); + fake_prefs_->SetString(kPrefsOmahaCohort, "evil\nstring"); + fake_prefs_->SetString(kPrefsOmahaCohortHint, "evil&string\\"); + fake_prefs_->SetString( + kPrefsOmahaCohortName, + base::JoinString(vector<string>(100, "My spoon is too big."), " ")); + tuc_params_.http_response = "invalid xml>"; + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_NE(string::npos, post_str_.find("testtheservice_pack>")); + EXPECT_EQ(string::npos, post_str_.find("testtheservice_pack>")); + EXPECT_NE(string::npos, post_str_.find("x86 generic<id")); + EXPECT_EQ(string::npos, post_str_.find("x86 generic<id")); + EXPECT_NE(string::npos, post_str_.find("unittest_track&lt;")); + EXPECT_EQ(string::npos, post_str_.find("unittest_track<")); + EXPECT_NE(string::npos, post_str_.find("unittest_hint&lt;")); + EXPECT_EQ(string::npos, post_str_.find("unittest_hint<")); + EXPECT_NE(string::npos, post_str_.find("<OEM MODEL>")); + EXPECT_EQ(string::npos, post_str_.find("<OEM MODEL>")); + EXPECT_NE(string::npos, post_str_.find("cohort=\"evil\nstring\"")); + EXPECT_EQ(string::npos, post_str_.find("cohorthint=\"evil&string\\\"")); + EXPECT_NE(string::npos, post_str_.find("cohorthint=\"evil&string\\\"")); + // Values from Prefs that are too big are removed from the XML instead of + // encoded. + EXPECT_EQ(string::npos, post_str_.find("cohortname=")); +} + +TEST_F(OmahaRequestActionTest, XmlDecodeTest) { + fake_update_response_.deadline = "<20110101"; + fake_update_response_.more_info_url = "testthe<url"; + fake_update_response_.codebase = "testthe&codebase/"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_EQ("testthe<url", response_.more_info_url); + EXPECT_EQ("testthe&codebase/file.signed", + response_.packages[0].payload_urls[0]); + EXPECT_EQ("<20110101", response_.deadline); +} + +TEST_F(OmahaRequestActionTest, ParseIntTest) { + // overflows int32_t: + fake_update_response_.size = 123123123123123ull; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_EQ(fake_update_response_.size, response_.packages[0].size); +} + +TEST_F(OmahaRequestActionTest, FormatUpdateCheckOutputTest) { + NiceMock<MockPrefs> prefs; + FakeSystemState::Get()->set_prefs(&prefs); + tuc_params_.http_response = "invalid xml>"; + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + EXPECT_CALL(prefs, GetString(kPrefsPreviousVersion, _)) + .WillOnce(DoAll(SetArgPointee<1>(string("")), Return(true))); + // An existing but empty previous version means that we didn't reboot to a new + // update, therefore, no need to update the previous version. + EXPECT_CALL(prefs, SetString(kPrefsPreviousVersion, _)).Times(0); + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_NE( + post_str_.find(" <ping active=\"1\" a=\"-1\" r=\"-1\"></ping>\n" + " <updatecheck></updatecheck>\n"), + string::npos); + EXPECT_NE(post_str_.find("hardware_class=\"OEM MODEL 09235 7471\""), + string::npos); + // No <event> tag should be sent if we didn't reboot to an update. + EXPECT_EQ(post_str_.find("<event"), string::npos); +} + +TEST_F(OmahaRequestActionTest, FormatSuccessEventOutputTest) { + TestEvent(new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted), + "invalid xml>"); + + string expected_event = base::StringPrintf( + " <event eventtype=\"%d\" eventresult=\"%d\"></event>\n", + OmahaEvent::kTypeUpdateDownloadStarted, + OmahaEvent::kResultSuccess); + EXPECT_NE(post_str_.find(expected_event), string::npos); + EXPECT_EQ(post_str_.find("ping"), string::npos); + EXPECT_EQ(post_str_.find("updatecheck"), string::npos); +} + +TEST_F(OmahaRequestActionTest, FormatErrorEventOutputTest) { + TestEvent(new OmahaEvent(OmahaEvent::kTypeDownloadComplete, + OmahaEvent::kResultError, + ErrorCode::kError), + "invalid xml>"); + + string expected_event = base::StringPrintf( + " <event eventtype=\"%d\" eventresult=\"%d\" " + "errorcode=\"%d\"></event>\n", + OmahaEvent::kTypeDownloadComplete, + OmahaEvent::kResultError, + static_cast<int>(ErrorCode::kError)); + EXPECT_NE(post_str_.find(expected_event), string::npos); + EXPECT_EQ(post_str_.find("updatecheck"), string::npos); +} + +TEST_F(OmahaRequestActionTest, IsEventTest) { + string http_response("doesn't matter"); + OmahaRequestAction update_check_action( + nullptr, + std::make_unique<MockHttpFetcher>( + http_response.data(), http_response.size(), nullptr), + false, + ""); + EXPECT_FALSE(update_check_action.IsEvent()); + + OmahaRequestAction event_action( + new OmahaEvent(OmahaEvent::kTypeUpdateComplete), + std::make_unique<MockHttpFetcher>( + http_response.data(), http_response.size(), nullptr), + false, + ""); + EXPECT_TRUE(event_action.IsEvent()); +} + +TEST_F(OmahaRequestActionTest, FormatDeltaOkayOutputTest) { + tuc_params_.http_response = "invalid xml>"; + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + for (int i = 0; i < 2; i++) { + bool delta_okay = i == 1; + const char* delta_okay_str = delta_okay ? "true" : "false"; + request_params_.set_delta_okay(delta_okay); + + ASSERT_FALSE(TestUpdateCheck()); + EXPECT_NE(post_str_.find( + base::StringPrintf(" delta_okay=\"%s\"", delta_okay_str)), + string::npos) + << "i = " << i; + } +} + +TEST_F(OmahaRequestActionTest, FormatInteractiveOutputTest) { + tuc_params_.http_response = "invalid xml>"; + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + for (int i = 0; i < 2; i++) { + bool interactive = i == 1; + const char* interactive_str = interactive ? "ondemandupdate" : "scheduler"; + request_params_.set_interactive(interactive); + + ASSERT_FALSE(TestUpdateCheck()); + EXPECT_NE(post_str_.find( + base::StringPrintf("installsource=\"%s\"", interactive_str)), + string::npos) + << "i = " << i; + } +} + +TEST_F(OmahaRequestActionTest, FormatTargetVersionPrefixOutputTest) { + tuc_params_.http_response = "invalid xml>"; + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + for (int i = 0; i < 2; i++) { + bool target_version_set = i == 1; + const char* target_version_prefix = target_version_set ? "10032." : ""; + request_params_.set_target_version_prefix(target_version_prefix); + + ASSERT_FALSE(TestUpdateCheck()); + if (target_version_set) { + EXPECT_NE(post_str_.find("<updatecheck targetversionprefix=\"10032.\">"), + string::npos) + << "i = " << i; + } else { + EXPECT_EQ(post_str_.find("targetversionprefix"), string::npos) + << "i = " << i; + } + } +} + +TEST_F(OmahaRequestActionTest, FormatRollbackAllowedOutputTest) { + tuc_params_.http_response = "invalid xml>"; + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + for (int i = 0; i < 4; i++) { + bool rollback_allowed = i / 2 == 0; + bool target_version_set = i % 2 == 0; + request_params_.set_target_version_prefix(target_version_set ? "10032." + : ""); + request_params_.set_rollback_allowed(rollback_allowed); + + ASSERT_FALSE(TestUpdateCheck()); + if (rollback_allowed && target_version_set) { + EXPECT_NE(post_str_.find("rollback_allowed=\"true\""), string::npos) + << "i = " << i; + } else { + EXPECT_EQ(post_str_.find("rollback_allowed"), string::npos) + << "i = " << i; + } + } +} + +TEST_F(OmahaRequestActionTest, OmahaEventTest) { + OmahaEvent default_event; + EXPECT_EQ(OmahaEvent::kTypeUnknown, default_event.type); + EXPECT_EQ(OmahaEvent::kResultError, default_event.result); + EXPECT_EQ(ErrorCode::kError, default_event.error_code); + + OmahaEvent success_event(OmahaEvent::kTypeUpdateDownloadStarted); + EXPECT_EQ(OmahaEvent::kTypeUpdateDownloadStarted, success_event.type); + EXPECT_EQ(OmahaEvent::kResultSuccess, success_event.result); + EXPECT_EQ(ErrorCode::kSuccess, success_event.error_code); + + OmahaEvent error_event(OmahaEvent::kTypeUpdateDownloadFinished, + OmahaEvent::kResultError, + ErrorCode::kError); + EXPECT_EQ(OmahaEvent::kTypeUpdateDownloadFinished, error_event.type); + EXPECT_EQ(OmahaEvent::kResultError, error_event.result); + EXPECT_EQ(ErrorCode::kError, error_event.error_code); +} + +TEST_F(OmahaRequestActionTest, DeviceQuickFixBuildTokenIsSetTest) { + // If DeviceQuickFixBuildToken value is set it takes precedence over pref + // value. + constexpr char autoupdate_token[] = "autoupdate_token>"; + constexpr char xml_encoded_autoupdate_token[] = "autoupdate_token>"; + constexpr char omaha_cohort_hint[] = "cohort_hint"; + + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + request_params_.set_autoupdate_token(autoupdate_token); + fake_prefs_->SetString(kPrefsOmahaCohortHint, omaha_cohort_hint); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_NE(string::npos, + post_str_.find("cohorthint=\"" + + string(xml_encoded_autoupdate_token) + "\"")); + EXPECT_EQ(string::npos, post_str_.find(autoupdate_token)); + EXPECT_EQ(string::npos, post_str_.find(omaha_cohort_hint)); +} + +TEST_F(OmahaRequestActionTest, DeviceQuickFixBuildTokenIsNotSetTest) { + // If DeviceQuickFixBuildToken is not set, pref value will be provided in + // cohorthint attribute. + constexpr char omaha_cohort_hint[] = "evil_string>"; + constexpr char xml_encoded_cohort_hint[] = "evil_string>"; + + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + fake_prefs_->SetString(kPrefsOmahaCohortHint, omaha_cohort_hint); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_NE( + string::npos, + post_str_.find("cohorthint=\"" + string(xml_encoded_cohort_hint) + "\"")); + EXPECT_EQ(string::npos, post_str_.find(omaha_cohort_hint)); +} + +TEST_F(OmahaRequestActionTest, TargetChannelHintTest) { + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + request_params_.set_lts_tag("hint>"); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_NE(string::npos, post_str_.find("ltstag=\"hint>\"")); +} + +void OmahaRequestActionTest::PingTest(bool ping_only) { + NiceMock<MockPrefs> prefs; + FakeSystemState::Get()->set_prefs(&prefs); + EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _)) + .Times(AnyNumber()); + EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); + // Add a few hours to the day difference to test no rounding, etc. + int64_t five_days_ago = + (Time::Now() - TimeDelta::FromHours(5 * 24 + 13)).ToInternalValue(); + int64_t six_days_ago = + (Time::Now() - TimeDelta::FromHours(6 * 24 + 11)).ToInternalValue(); + EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _)) + .WillOnce(DoAll(SetArgPointee<1>(0), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _)) + .WillOnce(DoAll(SetArgPointee<1>(six_days_ago), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _)) + .WillOnce(DoAll(SetArgPointee<1>(five_days_ago), Return(true))); + + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.ping_only = ping_only; + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_NE(post_str_.find("<ping active=\"1\" a=\"6\" r=\"5\"></ping>"), + string::npos); + if (ping_only) { + EXPECT_EQ(post_str_.find("updatecheck"), string::npos); + EXPECT_EQ(post_str_.find("previousversion"), string::npos); + } else { + EXPECT_NE(post_str_.find("updatecheck"), string::npos); + EXPECT_NE(post_str_.find("previousversion"), string::npos); + } +} + +TEST_F(OmahaRequestActionTest, PingTestSendOnlyAPing) { + PingTest(true /* ping_only */); +} + +TEST_F(OmahaRequestActionTest, PingTestSendAlsoAnUpdateCheck) { + PingTest(false /* ping_only */); +} + +TEST_F(OmahaRequestActionTest, ActivePingTest) { + NiceMock<MockPrefs> prefs; + FakeSystemState::Get()->set_prefs(&prefs); + EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _)) + .Times(AnyNumber()); + EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); + int64_t three_days_ago = + (Time::Now() - TimeDelta::FromHours(3 * 24 + 12)).ToInternalValue(); + int64_t now = Time::Now().ToInternalValue(); + EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _)) + .WillOnce(DoAll(SetArgPointee<1>(0), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _)) + .WillOnce(DoAll(SetArgPointee<1>(three_days_ago), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _)) + .WillOnce(DoAll(SetArgPointee<1>(now), Return(true))); + + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_NE(post_str_.find("<ping active=\"1\" a=\"3\"></ping>"), string::npos); +} + +TEST_F(OmahaRequestActionTest, RollCallPingTest) { + NiceMock<MockPrefs> prefs; + FakeSystemState::Get()->set_prefs(&prefs); + EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _)) + .Times(AnyNumber()); + EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); + int64_t four_days_ago = + (Time::Now() - TimeDelta::FromHours(4 * 24)).ToInternalValue(); + int64_t now = Time::Now().ToInternalValue(); + EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _)) + .WillOnce(DoAll(SetArgPointee<1>(0), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _)) + .WillOnce(DoAll(SetArgPointee<1>(now), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _)) + .WillOnce(DoAll(SetArgPointee<1>(four_days_ago), Return(true))); + + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_NE(post_str_.find("<ping active=\"1\" r=\"4\"></ping>\n"), + string::npos); +} + +TEST_F(OmahaRequestActionTest, NoPingTest) { + NiceMock<MockPrefs> prefs; + FakeSystemState::Get()->set_prefs(&prefs); + EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _)) + .Times(AnyNumber()); + EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); + int64_t one_hour_ago = + (Time::Now() - TimeDelta::FromHours(1)).ToInternalValue(); + EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _)) + .WillOnce(DoAll(SetArgPointee<1>(0), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _)) + .WillOnce(DoAll(SetArgPointee<1>(one_hour_ago), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _)) + .WillOnce(DoAll(SetArgPointee<1>(one_hour_ago), Return(true))); + // LastActivePingDay and PrefsLastRollCallPingDay are set even if we didn't + // send a ping. + EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)) + .WillOnce(Return(true)); + EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)) + .WillOnce(Return(true)); + + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_EQ(post_str_.find("ping"), string::npos); +} + +TEST_F(OmahaRequestActionTest, IgnoreEmptyPingTest) { + // This test ensures that we ignore empty ping only requests. + NiceMock<MockPrefs> prefs; + FakeSystemState::Get()->set_prefs(&prefs); + int64_t now = Time::Now().ToInternalValue(); + EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _)) + .WillOnce(DoAll(SetArgPointee<1>(now), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _)) + .WillOnce(DoAll(SetArgPointee<1>(now), Return(true))); + EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0); + EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0); + + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.ping_only = true; + tuc_params_.expected_check_result = metrics::CheckResult::kUnset; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + EXPECT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(post_str_.empty()); +} + +TEST_F(OmahaRequestActionTest, BackInTimePingTest) { + NiceMock<MockPrefs> prefs; + FakeSystemState::Get()->set_prefs(&prefs); + EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _)) + .Times(AnyNumber()); + EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); + int64_t future = + (Time::Now() + TimeDelta::FromHours(3 * 24 + 4)).ToInternalValue(); + EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _)) + .WillOnce(DoAll(SetArgPointee<1>(0), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _)) + .WillOnce(DoAll(SetArgPointee<1>(future), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _)) + .WillOnce(DoAll(SetArgPointee<1>(future), Return(true))); + EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)) + .WillOnce(Return(true)); + EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)) + .WillOnce(Return(true)); + + tuc_params_.http_response = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " + "protocol=\"3.0\"><daystart elapsed_seconds=\"100\"/>" + "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>" + "<updatecheck status=\"noupdate\"/></app></response>"; + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_EQ(post_str_.find("ping"), string::npos); +} + +TEST_F(OmahaRequestActionTest, LastPingDayUpdateTest) { + // This test checks that the action updates the last ping day to now + // minus 200 seconds with a slack of 5 seconds. Therefore, the test + // may fail if it runs for longer than 5 seconds. It shouldn't run + // that long though. + int64_t midnight = + (Time::Now() - TimeDelta::FromSeconds(200)).ToInternalValue(); + int64_t midnight_slack = + (Time::Now() - TimeDelta::FromSeconds(195)).ToInternalValue(); + NiceMock<MockPrefs> prefs; + FakeSystemState::Get()->set_prefs(&prefs); + EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber()); + EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); + EXPECT_CALL(prefs, + SetInt64(kPrefsLastActivePingDay, + AllOf(Ge(midnight), Le(midnight_slack)))) + .WillOnce(Return(true)); + EXPECT_CALL(prefs, + SetInt64(kPrefsLastRollCallPingDay, + AllOf(Ge(midnight), Le(midnight_slack)))) + .WillOnce(Return(true)); + + tuc_params_.http_response = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " + "protocol=\"3.0\"><daystart elapsed_seconds=\"200\"/>" + "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>" + "<updatecheck status=\"noupdate\"/></app></response>"; + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); +} + +TEST_F(OmahaRequestActionTest, NoElapsedSecondsTest) { + NiceMock<MockPrefs> prefs; + FakeSystemState::Get()->set_prefs(&prefs); + EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber()); + EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); + EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0); + EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0); + + tuc_params_.http_response = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " + "protocol=\"3.0\"><daystart blah=\"200\"/>" + "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>" + "<updatecheck status=\"noupdate\"/></app></response>"; + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); +} + +TEST_F(OmahaRequestActionTest, BadElapsedSecondsTest) { + NiceMock<MockPrefs> prefs; + FakeSystemState::Get()->set_prefs(&prefs); + EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber()); + EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); + EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0); + EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0); + + tuc_params_.http_response = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " + "protocol=\"3.0\"><daystart elapsed_seconds=\"x\"/>" + "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>" + "<updatecheck status=\"noupdate\"/></app></response>"; + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); +} + +TEST_F(OmahaRequestActionTest, NoUniqueIDTest) { + tuc_params_.http_response = "invalid xml>"; + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_EQ(post_str_.find("machineid="), string::npos); + EXPECT_EQ(post_str_.find("userid="), string::npos); +} + +TEST_F(OmahaRequestActionTest, NetworkFailureTest) { + const int http_error_code = + static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 501; + tuc_params_.fail_http_response_code = 501; + tuc_params_.expected_code = static_cast<ErrorCode>(http_error_code); + tuc_params_.expected_check_result = metrics::CheckResult::kDownloadError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + tuc_params_.expected_download_error_code = + static_cast<metrics::DownloadErrorCode>(501); + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, NetworkFailureBadHTTPCodeTest) { + const int http_error_code = + static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 999; + + tuc_params_.fail_http_response_code = 1500; + tuc_params_.expected_code = static_cast<ErrorCode>(http_error_code); + tuc_params_.expected_check_result = metrics::CheckResult::kDownloadError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + tuc_params_.expected_download_error_code = + metrics::DownloadErrorCode::kHttpStatusOther; + + ASSERT_FALSE(TestUpdateCheck()); + EXPECT_FALSE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsPersistedFirstTime) { + request_params_.set_wall_clock_based_wait_enabled(true); + request_params_.set_waiting_period(TimeDelta().FromDays(1)); + request_params_.set_update_check_count_wait_enabled(false); + + Time arbitrary_date; + ASSERT_TRUE(Time::FromString("6/4/1989", &arbitrary_date)); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(arbitrary_date); + + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_code = ErrorCode::kOmahaUpdateDeferredPerPolicy; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kDeferring; + + ASSERT_FALSE(TestUpdateCheck()); + + int64_t timestamp = 0; + ASSERT_TRUE(fake_prefs_->GetInt64(kPrefsUpdateFirstSeenAt, ×tamp)); + EXPECT_EQ(arbitrary_date.ToInternalValue(), timestamp); + EXPECT_FALSE(response_.update_exists); + + // Verify if we are interactive check we don't defer. + request_params_.set_interactive(true); + tuc_params_.expected_code = ErrorCode::kSuccess; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUpdating; + + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsUsedIfAlreadyPresent) { + request_params_.set_wall_clock_based_wait_enabled(true); + request_params_.set_waiting_period(TimeDelta().FromDays(1)); + request_params_.set_update_check_count_wait_enabled(false); + + Time t1, t2; + ASSERT_TRUE(Time::FromString("1/1/2012", &t1)); + ASSERT_TRUE(Time::FromString("1/3/2012", &t2)); + ASSERT_TRUE( + fake_prefs_->SetInt64(kPrefsUpdateFirstSeenAt, t1.ToInternalValue())); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(t2); + + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); + + // Make sure the timestamp t1 is unchanged showing that it was reused. + int64_t timestamp = 0; + ASSERT_TRUE(fake_prefs_->GetInt64(kPrefsUpdateFirstSeenAt, ×tamp)); + ASSERT_TRUE(timestamp == t1.ToInternalValue()); +} + +TEST_F(OmahaRequestActionTest, TestChangingToMoreStableChannel) { + // Create a uniquely named test directory. + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + + request_params_.set_root(tempdir.GetPath().value()); + request_params_.set_app_id("{22222222-2222-2222-2222-222222222222}"); + request_params_.set_app_version("1.2.3.4"); + request_params_.set_product_components("o.bundle=1"); + request_params_.set_current_channel("canary-channel"); + EXPECT_TRUE( + request_params_.SetTargetChannel("stable-channel", true, nullptr)); + request_params_.UpdateDownloadChannel(); + EXPECT_TRUE(request_params_.ShouldPowerwash()); + + tuc_params_.http_response = "invalid xml>"; + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_NE(string::npos, + post_str_.find( + "appid=\"{22222222-2222-2222-2222-222222222222}\" " + "version=\"0.0.0.0\" from_version=\"1.2.3.4\" " + "track=\"stable-channel\" from_track=\"canary-channel\" ")); + EXPECT_EQ(string::npos, post_str_.find("o.bundle")); +} + +TEST_F(OmahaRequestActionTest, TestChangingToLessStableChannel) { + // Create a uniquely named test directory. + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + + request_params_.set_root(tempdir.GetPath().value()); + request_params_.set_app_id("{11111111-1111-1111-1111-111111111111}"); + request_params_.set_app_version("5.6.7.8"); + request_params_.set_product_components("o.bundle=1"); + request_params_.set_current_channel("stable-channel"); + EXPECT_TRUE( + request_params_.SetTargetChannel("canary-channel", false, nullptr)); + request_params_.UpdateDownloadChannel(); + EXPECT_FALSE(request_params_.ShouldPowerwash()); + + tuc_params_.http_response = "invalid xml>"; + tuc_params_.expected_code = ErrorCode::kOmahaRequestXMLParseError; + tuc_params_.expected_check_result = metrics::CheckResult::kParsingError; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_FALSE(TestUpdateCheck()); + + EXPECT_NE( + string::npos, + post_str_.find("appid=\"{11111111-1111-1111-1111-111111111111}\" " + "version=\"5.6.7.8\" " + "track=\"canary-channel\" from_track=\"stable-channel\"")); + EXPECT_EQ(string::npos, post_str_.find("from_version")); + EXPECT_NE(string::npos, post_str_.find("o.bundle.version=\"1\"")); +} + +// Checks that the initial ping with a=-1 r=-1 is not send when the device +// was powerwashed. +TEST_F(OmahaRequestActionTest, PingWhenPowerwashed) { + fake_prefs_->SetString(kPrefsPreviousVersion, ""); + + // Flag that the device was powerwashed in the past. + FakeSystemState::Get()->fake_hardware()->SetPowerwashCount(1); + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + + // We shouldn't send a ping in this case since powerwash > 0. + EXPECT_EQ(string::npos, post_str_.find("<ping")); +} + +// Checks that the initial ping with a=-1 r=-1 is not send when the device +// first_active_omaha_ping_sent is set. +TEST_F(OmahaRequestActionTest, PingWhenFirstActiveOmahaPingIsSent) { + fake_prefs_->SetString(kPrefsPreviousVersion, ""); + + // Flag that the device was not powerwashed in the past. + FakeSystemState::Get()->fake_hardware()->SetPowerwashCount(0); + + // Flag that the device has sent first active ping in the past. + FakeSystemState::Get()->fake_hardware()->SetFirstActiveOmahaPingSent(); + + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + + // We shouldn't send a ping in this case since + // first_active_omaha_ping_sent=true + EXPECT_EQ(string::npos, post_str_.find("<ping")); +} + +// Checks that the event 54 is sent on a reboot to a new update. +TEST_F(OmahaRequestActionTest, RebootAfterUpdateEvent) { + // Flag that the device was updated in a previous boot. + fake_prefs_->SetString(kPrefsPreviousVersion, "1.2.3.4"); + + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + + // An event 54 is included and has the right version. + EXPECT_NE( + string::npos, + post_str_.find(base::StringPrintf("<event eventtype=\"%d\"", + OmahaEvent::kTypeRebootedAfterUpdate))); + EXPECT_NE(string::npos, + post_str_.find("previousversion=\"1.2.3.4\"></event>")); + + // The previous version flag should have been removed. + EXPECT_TRUE(fake_prefs_->Exists(kPrefsPreviousVersion)); + string prev_version; + EXPECT_TRUE(fake_prefs_->GetString(kPrefsPreviousVersion, &prev_version)); + EXPECT_TRUE(prev_version.empty()); +} + +void OmahaRequestActionTest::P2PTest(bool initial_allow_p2p_for_downloading, + bool initial_allow_p2p_for_sharing, + bool omaha_disable_p2p_for_downloading, + bool omaha_disable_p2p_for_sharing, + bool payload_state_allow_p2p_attempt, + bool expect_p2p_client_lookup, + const string& p2p_client_result_url, + bool expected_allow_p2p_for_downloading, + bool expected_allow_p2p_for_sharing, + const string& expected_p2p_url) { + bool actual_allow_p2p_for_downloading = initial_allow_p2p_for_downloading; + bool actual_allow_p2p_for_sharing = initial_allow_p2p_for_sharing; + string actual_p2p_url; + + MockPayloadState mock_payload_state; + FakeSystemState::Get()->set_payload_state(&mock_payload_state); + EXPECT_CALL(mock_payload_state, P2PAttemptAllowed()) + .WillRepeatedly(Return(payload_state_allow_p2p_attempt)); + EXPECT_CALL(mock_payload_state, GetUsingP2PForDownloading()) + .WillRepeatedly(ReturnPointee(&actual_allow_p2p_for_downloading)); + EXPECT_CALL(mock_payload_state, GetUsingP2PForSharing()) + .WillRepeatedly(ReturnPointee(&actual_allow_p2p_for_sharing)); + EXPECT_CALL(mock_payload_state, SetUsingP2PForDownloading(_)) + .WillRepeatedly(SaveArg<0>(&actual_allow_p2p_for_downloading)); + EXPECT_CALL(mock_payload_state, SetUsingP2PForSharing(_)) + .WillRepeatedly(SaveArg<0>(&actual_allow_p2p_for_sharing)); + EXPECT_CALL(mock_payload_state, SetP2PUrl(_)) + .WillRepeatedly(SaveArg<0>(&actual_p2p_url)); + + MockP2PManager mock_p2p_manager; + FakeSystemState::Get()->set_p2p_manager(&mock_p2p_manager); + mock_p2p_manager.fake().SetLookupUrlForFileResult(p2p_client_result_url); + + TimeDelta timeout = TimeDelta::FromSeconds(kMaxP2PNetworkWaitTimeSeconds); + EXPECT_CALL(mock_p2p_manager, LookupUrlForFile(_, _, timeout, _)) + .Times(expect_p2p_client_lookup ? 1 : 0); + + fake_update_response_.disable_p2p_for_downloading = + omaha_disable_p2p_for_downloading; + fake_update_response_.disable_p2p_for_sharing = omaha_disable_p2p_for_sharing; + + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kUpdateAvailable; + + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); + + EXPECT_EQ(omaha_disable_p2p_for_downloading, + response_.disable_p2p_for_downloading); + EXPECT_EQ(omaha_disable_p2p_for_sharing, response_.disable_p2p_for_sharing); + + EXPECT_EQ(expected_allow_p2p_for_downloading, + actual_allow_p2p_for_downloading); + EXPECT_EQ(expected_allow_p2p_for_sharing, actual_allow_p2p_for_sharing); + EXPECT_EQ(expected_p2p_url, actual_p2p_url); +} + +TEST_F(OmahaRequestActionTest, P2PWithPeer) { + P2PTest(true, // initial_allow_p2p_for_downloading + true, // initial_allow_p2p_for_sharing + false, // omaha_disable_p2p_for_downloading + false, // omaha_disable_p2p_for_sharing + true, // payload_state_allow_p2p_attempt + true, // expect_p2p_client_lookup + "http://1.3.5.7/p2p", // p2p_client_result_url + true, // expected_allow_p2p_for_downloading + true, // expected_allow_p2p_for_sharing + "http://1.3.5.7/p2p"); // expected_p2p_url +} + +TEST_F(OmahaRequestActionTest, P2PWithoutPeer) { + P2PTest(true, // initial_allow_p2p_for_downloading + true, // initial_allow_p2p_for_sharing + false, // omaha_disable_p2p_for_downloading + false, // omaha_disable_p2p_for_sharing + true, // payload_state_allow_p2p_attempt + true, // expect_p2p_client_lookup + "", // p2p_client_result_url + false, // expected_allow_p2p_for_downloading + true, // expected_allow_p2p_for_sharing + ""); // expected_p2p_url +} + +TEST_F(OmahaRequestActionTest, P2PDownloadNotAllowed) { + P2PTest(false, // initial_allow_p2p_for_downloading + true, // initial_allow_p2p_for_sharing + false, // omaha_disable_p2p_for_downloading + false, // omaha_disable_p2p_for_sharing + true, // payload_state_allow_p2p_attempt + false, // expect_p2p_client_lookup + "unset", // p2p_client_result_url + false, // expected_allow_p2p_for_downloading + true, // expected_allow_p2p_for_sharing + ""); // expected_p2p_url +} + +TEST_F(OmahaRequestActionTest, P2PWithPeerDownloadDisabledByOmaha) { + P2PTest(true, // initial_allow_p2p_for_downloading + true, // initial_allow_p2p_for_sharing + true, // omaha_disable_p2p_for_downloading + false, // omaha_disable_p2p_for_sharing + true, // payload_state_allow_p2p_attempt + false, // expect_p2p_client_lookup + "unset", // p2p_client_result_url + false, // expected_allow_p2p_for_downloading + true, // expected_allow_p2p_for_sharing + ""); // expected_p2p_url +} + +TEST_F(OmahaRequestActionTest, P2PWithPeerSharingDisabledByOmaha) { + P2PTest(true, // initial_allow_p2p_for_downloading + true, // initial_allow_p2p_for_sharing + false, // omaha_disable_p2p_for_downloading + true, // omaha_disable_p2p_for_sharing + true, // payload_state_allow_p2p_attempt + true, // expect_p2p_client_lookup + "http://1.3.5.7/p2p", // p2p_client_result_url + true, // expected_allow_p2p_for_downloading + false, // expected_allow_p2p_for_sharing + "http://1.3.5.7/p2p"); // expected_p2p_url +} + +TEST_F(OmahaRequestActionTest, P2PWithPeerBothDisabledByOmaha) { + P2PTest(true, // initial_allow_p2p_for_downloading + true, // initial_allow_p2p_for_sharing + true, // omaha_disable_p2p_for_downloading + true, // omaha_disable_p2p_for_sharing + true, // payload_state_allow_p2p_attempt + false, // expect_p2p_client_lookup + "unset", // p2p_client_result_url + false, // expected_allow_p2p_for_downloading + false, // expected_allow_p2p_for_sharing + ""); // expected_p2p_url +} + +bool OmahaRequestActionTest::InstallDateParseHelper(const string& elapsed_days, + OmahaResponse* response) { + fake_update_response_.elapsed_days = elapsed_days; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + return TestUpdateCheck(); +} + +TEST_F(OmahaRequestActionTest, ParseInstallDateFromResponse) { + // Simulate a successful update check that happens during OOBE. The + // deadline in the response is needed to force the update attempt to + // occur; responses without a deadline seen during OOBE will normally + // return ErrorCode::kNonCriticalUpdateInOOBE. + FakeSystemState::Get()->fake_hardware()->UnsetIsOOBEComplete(); + fake_update_response_.deadline = "20101020"; + + // Check that we parse elapsed_days in the Omaha Response correctly. + // and that the kPrefsInstallDateDays value is written to. + EXPECT_FALSE(fake_prefs_->Exists(kPrefsInstallDateDays)); + EXPECT_TRUE(InstallDateParseHelper("42", &response_)); + EXPECT_TRUE(response_.update_exists); + EXPECT_EQ(42, response_.install_date_days); + EXPECT_TRUE(fake_prefs_->Exists(kPrefsInstallDateDays)); + int64_t prefs_days; + EXPECT_TRUE(fake_prefs_->GetInt64(kPrefsInstallDateDays, &prefs_days)); + EXPECT_EQ(prefs_days, 42); + + // If there already is a value set, we shouldn't do anything. + EXPECT_TRUE(InstallDateParseHelper("7", &response_)); + EXPECT_TRUE(response_.update_exists); + EXPECT_EQ(7, response_.install_date_days); + EXPECT_TRUE(fake_prefs_->GetInt64(kPrefsInstallDateDays, &prefs_days)); + EXPECT_EQ(prefs_days, 42); + + // Note that elapsed_days is not necessarily divisible by 7 so check + // that we round down correctly when populating kPrefsInstallDateDays. + EXPECT_TRUE(fake_prefs_->Delete(kPrefsInstallDateDays)); + EXPECT_TRUE(InstallDateParseHelper("23", &response_)); + EXPECT_TRUE(response_.update_exists); + EXPECT_EQ(23, response_.install_date_days); + EXPECT_TRUE(fake_prefs_->GetInt64(kPrefsInstallDateDays, &prefs_days)); + EXPECT_EQ(prefs_days, 21); + + // Check that we correctly handle elapsed_days not being included in + // the Omaha Response_. + EXPECT_TRUE(InstallDateParseHelper("", &response_)); + EXPECT_TRUE(response_.update_exists); + EXPECT_EQ(-1, response_.install_date_days); +} + +// If there is no prefs and OOBE is not complete, we should not +// report anything to Omaha. +TEST_F(OmahaRequestActionTest, GetInstallDateWhenNoPrefsNorOOBE) { + FakeSystemState::Get()->fake_hardware()->UnsetIsOOBEComplete(); + EXPECT_EQ(OmahaRequestAction::GetInstallDate(), -1); + EXPECT_FALSE(fake_prefs_->Exists(kPrefsInstallDateDays)); +} + +// If OOBE is complete and happened on a valid date (e.g. after Jan +// 1 2007 0:00 PST), that date should be used and written to +// prefs. However, first try with an invalid date and check we do +// nothing. +TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedWithInvalidDate) { + Time oobe_date = Time::FromTimeT(42); // Dec 31, 1969 16:00:42 PST. + FakeSystemState::Get()->fake_hardware()->SetIsOOBEComplete(oobe_date); + EXPECT_EQ(OmahaRequestAction::GetInstallDate(), -1); + EXPECT_FALSE(fake_prefs_->Exists(kPrefsInstallDateDays)); +} + +// Then check with a valid date. The date Jan 20, 2007 0:00 PST +// should yield an InstallDate of 14. +TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedWithValidDate) { + Time oobe_date = Time::FromTimeT(1169280000); // Jan 20, 2007 0:00 PST. + FakeSystemState::Get()->fake_hardware()->SetIsOOBEComplete(oobe_date); + EXPECT_EQ(OmahaRequestAction::GetInstallDate(), 14); + EXPECT_TRUE(fake_prefs_->Exists(kPrefsInstallDateDays)); + + int64_t prefs_days; + EXPECT_TRUE(fake_prefs_->GetInt64(kPrefsInstallDateDays, &prefs_days)); + EXPECT_EQ(prefs_days, 14); +} + +// Now that we have a valid date in prefs, check that we keep using +// that even if OOBE date reports something else. The date Jan 30, +// 2007 0:00 PST should yield an InstallDate of 28... but since +// there's a prefs file, we should still get 14. +TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedDateChanges) { + // Set a valid date in the prefs first. + EXPECT_TRUE(fake_prefs_->SetInt64(kPrefsInstallDateDays, 14)); + + Time oobe_date = Time::FromTimeT(1170144000); // Jan 30, 2007 0:00 PST. + FakeSystemState::Get()->fake_hardware()->SetIsOOBEComplete(oobe_date); + EXPECT_EQ(OmahaRequestAction::GetInstallDate(), 14); + + int64_t prefs_days; + EXPECT_TRUE(fake_prefs_->GetInt64(kPrefsInstallDateDays, &prefs_days)); + EXPECT_EQ(prefs_days, 14); + + // If we delete the prefs file, we should get 28 days. + EXPECT_TRUE(fake_prefs_->Delete(kPrefsInstallDateDays)); + EXPECT_EQ(OmahaRequestAction::GetInstallDate(), 28); + EXPECT_TRUE(fake_prefs_->GetInt64(kPrefsInstallDateDays, &prefs_days)); + EXPECT_EQ(prefs_days, 28); +} + +// Verifies that a device with no device policy, and is not a consumer +// device sets the max kernel key version to the current version. +// ie. the same behavior as if rollback is enabled. +TEST_F(OmahaRequestActionTest, NoPolicyEnterpriseDevicesSetMaxRollback) { + FakeHardware* fake_hw = FakeSystemState::Get()->fake_hardware(); + + // Setup and verify some initial default values for the kernel TPM + // values that control verified boot and rollback. + const int min_kernel_version = 4; + fake_hw->SetMinKernelKeyVersion(min_kernel_version); + fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity); + EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); + EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward()); + + EXPECT_CALL( + *FakeSystemState::Get()->mock_metrics_reporter(), + ReportKeyVersionMetrics(min_kernel_version, min_kernel_version, true)) + .Times(1); + + fake_update_response_.deadline = "20101020"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.is_consumer_device = false; + tuc_params_.rollback_allowed_milestones = 3; + + EXPECT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); + + // Verify kernel_max_rollforward was set to the current minimum + // kernel key version. This has the effect of freezing roll + // forwards indefinitely. This will hold the rollback window + // open until a future change will be able to move this forward + // relative the configured window. + EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); + EXPECT_EQ(min_kernel_version, fake_hw->GetMaxKernelKeyRollforward()); +} + +// Verifies that a conmsumer device with no device policy sets the +// max kernel key version to the current version. ie. the same +// behavior as if rollback is enabled. +TEST_F(OmahaRequestActionTest, NoPolicyConsumerDevicesSetMaxRollback) { + FakeHardware* fake_hw = FakeSystemState::Get()->fake_hardware(); + + // Setup and verify some initial default values for the kernel TPM + // values that control verified boot and rollback. + const int min_kernel_version = 3; + fake_hw->SetMinKernelKeyVersion(min_kernel_version); + fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity); + EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); + EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward()); + + EXPECT_CALL( + *FakeSystemState::Get()->mock_metrics_reporter(), + ReportKeyVersionMetrics(min_kernel_version, kRollforwardInfinity, true)) + .Times(1); + + fake_update_response_.deadline = "20101020"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.is_consumer_device = true; + tuc_params_.rollback_allowed_milestones = 3; + + EXPECT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); + + // Verify that with rollback disabled that kernel_max_rollforward + // was set to logical infinity. This is the expected behavior for + // consumer devices and matches the existing behavior prior to the + // rollback features. + EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); + EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward()); +} + +// Verifies that a device with rollback enabled sets kernel_max_rollforward +// in the TPM to prevent roll forward. +TEST_F(OmahaRequestActionTest, RollbackEnabledDevicesSetMaxRollback) { + FakeHardware* fake_hw = FakeSystemState::Get()->fake_hardware(); + + // Setup and verify some initial default values for the kernel TPM + // values that control verified boot and rollback. + const int allowed_milestones = 4; + const int min_kernel_version = 3; + fake_hw->SetMinKernelKeyVersion(min_kernel_version); + fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity); + EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); + EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward()); + + EXPECT_CALL( + *FakeSystemState::Get()->mock_metrics_reporter(), + ReportKeyVersionMetrics(min_kernel_version, min_kernel_version, true)) + .Times(1); + + fake_update_response_.deadline = "20101020"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.is_consumer_device = false; + tuc_params_.rollback_allowed_milestones = allowed_milestones; + tuc_params_.is_policy_loaded = true; + + EXPECT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); + + // Verify that with rollback enabled that kernel_max_rollforward + // was set to the current minimum kernel key version. This has + // the effect of freezing roll forwards indefinitely. This will + // hold the rollback window open until a future change will + // be able to move this forward relative the configured window. + EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); + EXPECT_EQ(min_kernel_version, fake_hw->GetMaxKernelKeyRollforward()); +} + +// Verifies that a device with rollback disabled sets kernel_max_rollforward +// in the TPM to logical infinity, to allow roll forward. +TEST_F(OmahaRequestActionTest, RollbackDisabledDevicesSetMaxRollback) { + FakeHardware* fake_hw = FakeSystemState::Get()->fake_hardware(); + + // Setup and verify some initial default values for the kernel TPM + // values that control verified boot and rollback. + const int allowed_milestones = 0; + const int min_kernel_version = 3; + fake_hw->SetMinKernelKeyVersion(min_kernel_version); + fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity); + EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); + EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward()); + + EXPECT_CALL( + *FakeSystemState::Get()->mock_metrics_reporter(), + ReportKeyVersionMetrics(min_kernel_version, kRollforwardInfinity, true)) + .Times(1); + + fake_update_response_.deadline = "20101020"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.is_consumer_device = false; + tuc_params_.rollback_allowed_milestones = allowed_milestones; + tuc_params_.is_policy_loaded = true; + + EXPECT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); + + // Verify that with rollback disabled that kernel_max_rollforward + // was set to logical infinity. + EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); + EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward()); +} + +TEST_F(OmahaRequestActionTest, RollbackResponseParsedNoEntries) { + fake_update_response_.rollback = true; + fake_update_response_.deadline = "20101020"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.is_consumer_device = false; + tuc_params_.rollback_allowed_milestones = 4; + tuc_params_.is_policy_loaded = true; + + EXPECT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); + EXPECT_TRUE(response_.is_rollback); +} + +TEST_F(OmahaRequestActionTest, RollbackResponseValidVersionsParsed) { + fake_update_response_.rollback_firmware_version = "1.2"; + fake_update_response_.rollback_kernel_version = "3.4"; + fake_update_response_.rollback = true; + fake_update_response_.deadline = "20101020"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.is_consumer_device = false; + tuc_params_.rollback_allowed_milestones = 4; + tuc_params_.is_policy_loaded = true; + + EXPECT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); + EXPECT_TRUE(response_.is_rollback); + EXPECT_EQ(1, response_.rollback_key_version.firmware_key); + EXPECT_EQ(2, response_.rollback_key_version.firmware); + EXPECT_EQ(3, response_.rollback_key_version.kernel_key); + EXPECT_EQ(4, response_.rollback_key_version.kernel); +} + +TEST_F(OmahaRequestActionTest, + TestUpdateFirstSeenAtPrefPersistedIfUpdateExists) { + FakeClock fake_clock; + Time now = Time::Now(); + fake_clock.SetWallclockTime(now); + FakeSystemState::Get()->set_clock(&fake_clock); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); + EXPECT_TRUE(fake_prefs_->Exists(kPrefsUpdateFirstSeenAt)); + + int64_t stored_first_seen_at_time; + EXPECT_TRUE(fake_prefs_->GetInt64(kPrefsUpdateFirstSeenAt, + &stored_first_seen_at_time)); + EXPECT_EQ(now.ToInternalValue(), stored_first_seen_at_time); +} + +TEST_F(OmahaRequestActionTest, + TestUpdateFirstSeenAtPrefNotPersistedIfUpdateFails) { + FakeClock fake_clock; + Time now = Time::Now(); + fake_clock.SetWallclockTime(now); + FakeSystemState::Get()->set_clock(&fake_clock); + + tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse(); + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_FALSE(response_.update_exists); + EXPECT_FALSE(fake_prefs_->Exists(kPrefsUpdateFirstSeenAt)); +} + +TEST_F(OmahaRequestActionTest, InstallTest) { + request_params_.set_is_install(true); + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}}, + {request_params_.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}}); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + for (const auto& it : request_params_.dlc_apps_params()) { + EXPECT_NE(string::npos, post_str_.find("appid=\"" + it.first + "\"")); + } + EXPECT_NE(string::npos, + post_str_.find("appid=\"" + fake_update_response_.app_id + "\"")); + + // Count number of updatecheck tag in response_. + int updatecheck_count = 0; + size_t pos = 0; + while ((pos = post_str_.find("<updatecheck", pos)) != string::npos) { + updatecheck_count++; + pos++; + } + EXPECT_EQ(request_params_.dlc_apps_params().size(), updatecheck_count); + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, InstallMissingPlatformVersionTest) { + fake_update_response_.multi_app_skip_updatecheck = true; + fake_update_response_.multi_app_no_update = false; + request_params_.set_is_install(true); + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}}, + {request_params_.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}}); + request_params_.set_app_id(fake_update_response_.app_id_skip_updatecheck); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); + EXPECT_EQ(fake_update_response_.version, response_.version); +} + +TEST_F(OmahaRequestActionTest, UpdateWithDlcTest) { + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}}); + fake_update_response_.dlc_app_update = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false)); + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_EQ(response_.packages.size(), 2u); + // Two candidate URLs. + EXPECT_EQ(response_.packages[1].payload_urls.size(), 2u); + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, UpdateWithPartiallyExcludedDlcTest) { + const string kDlcAppId = request_params_.GetDlcAppId(kDlcId1); + request_params_.set_dlc_apps_params({{kDlcAppId, {.name = kDlcId1}}}); + fake_update_response_.dlc_app_update = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + // The first DLC candidate URL is excluded. + EXPECT_CALL(mock_excluder_, IsExcluded(_)) + .WillOnce(Return(true)) + .WillOnce(Return(false)); + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_EQ(response_.packages.size(), 2u); + // One candidate URL. + EXPECT_EQ(response_.packages[1].payload_urls.size(), 1u); + EXPECT_TRUE(response_.update_exists); + EXPECT_TRUE(request_params_.dlc_apps_params().at(kDlcAppId).updated); +} + +TEST_F(OmahaRequestActionTest, UpdateWithExcludedDlcTest) { + const string kDlcAppId = request_params_.GetDlcAppId(kDlcId1); + request_params_.set_dlc_apps_params({{kDlcAppId, {.name = kDlcId1}}}); + fake_update_response_.dlc_app_update = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + // Both DLC candidate URLs are excluded. + EXPECT_CALL(mock_excluder_, IsExcluded(_)) + .WillOnce(Return(true)) + .WillOnce(Return(true)); + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_EQ(response_.packages.size(), 1u); + EXPECT_TRUE(response_.update_exists); + EXPECT_FALSE(request_params_.dlc_apps_params().at(kDlcAppId).updated); +} + +TEST_F(OmahaRequestActionTest, UpdateWithDeprecatedDlcTest) { + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId(kDlcId2), {.name = kDlcId2}}}); + fake_update_response_.dlc_app_no_update = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false)); + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, UpdateWithDlcAndDeprecatedDlcTest) { + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}, + {request_params_.GetDlcAppId(kDlcId2), {.name = kDlcId2}}}); + fake_update_response_.dlc_app_update = true; + fake_update_response_.dlc_app_no_update = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false)); + ASSERT_TRUE(TestUpdateCheck()); + + EXPECT_TRUE(response_.update_exists); +} + +TEST_F(OmahaRequestActionTest, PastRollbackVersionsNoEntries) { + fake_update_response_.rollback = true; + fake_update_response_.rollback_allowed_milestones = 4; + request_params_.set_rollback_allowed_milestones(4); + fake_update_response_.deadline = "20101020"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.is_consumer_device = false; + tuc_params_.rollback_allowed_milestones = 4; + tuc_params_.is_policy_loaded = true; + + EXPECT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); + EXPECT_TRUE(response_.is_rollback); + EXPECT_EQ(std::numeric_limits<uint16_t>::max(), + response_.past_rollback_key_version.firmware_key); + EXPECT_EQ(std::numeric_limits<uint16_t>::max(), + response_.past_rollback_key_version.firmware); + EXPECT_EQ(std::numeric_limits<uint16_t>::max(), + response_.past_rollback_key_version.kernel_key); + EXPECT_EQ(std::numeric_limits<uint16_t>::max(), + response_.past_rollback_key_version.kernel); +} + +TEST_F(OmahaRequestActionTest, PastRollbackVersionsValidEntries) { + request_params_.set_rollback_allowed_milestones(4); + fake_update_response_.rollback = true; + fake_update_response_.rollback_allowed_milestones = 4; + fake_update_response_.rollback_firmware_version = "4.3"; + fake_update_response_.rollback_kernel_version = "2.1"; + fake_update_response_.past_rollback_key_version = + std::make_pair("16.15", "14.13"); + fake_update_response_.deadline = "20101020"; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.is_consumer_device = false; + tuc_params_.rollback_allowed_milestones = 4; + tuc_params_.is_policy_loaded = true; + + EXPECT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); + EXPECT_TRUE(response_.is_rollback); + EXPECT_EQ(16, response_.past_rollback_key_version.firmware_key); + EXPECT_EQ(15, response_.past_rollback_key_version.firmware); + EXPECT_EQ(14, response_.past_rollback_key_version.kernel_key); + EXPECT_EQ(13, response_.past_rollback_key_version.kernel); +} + +TEST_F(OmahaRequestActionTest, MismatchNumberOfVersions) { + fake_update_response_.rollback = true; + fake_update_response_.rollback_allowed_milestones = 2; + fake_update_response_.deadline = "20101020"; + request_params_.set_rollback_allowed_milestones(4); + + // Since |request_params_.rollback_allowed_milestones| is 4 but the response + // is constructed with |fake_update_response_.rollback_allowed_milestones| set + // to 2, OmahaRequestAction will look for the key values of N-4 version but + // only the N-2 version will exist. + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + tuc_params_.is_consumer_device = false; + tuc_params_.rollback_allowed_milestones = 2; + tuc_params_.is_policy_loaded = true; + + EXPECT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(response_.update_exists); + EXPECT_TRUE(response_.is_rollback); + EXPECT_EQ(std::numeric_limits<uint16_t>::max(), + response_.past_rollback_key_version.firmware_key); + EXPECT_EQ(std::numeric_limits<uint16_t>::max(), + response_.past_rollback_key_version.firmware); + EXPECT_EQ(std::numeric_limits<uint16_t>::max(), + response_.past_rollback_key_version.kernel_key); + EXPECT_EQ(std::numeric_limits<uint16_t>::max(), + response_.past_rollback_key_version.kernel); +} + +TEST_F(OmahaRequestActionTest, IncludeRequisitionTest) { + request_params_.set_device_requisition("remora"); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_NE(string::npos, post_str_.find("requisition=\"remora\"")); +} + +TEST_F(OmahaRequestActionTest, NoIncludeRequisitionTest) { + request_params_.set_device_requisition(""); + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_EQ(string::npos, post_str_.find("requisition")); +} + +TEST_F(OmahaRequestActionTest, PersistEolDateTest) { + tuc_params_.http_response = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " + "protocol=\"3.0\"><app appid=\"test-app-id\" status=\"ok\">" + "<ping status=\"ok\"/><updatecheck status=\"noupdate\" " + "_eol_date=\"200\" _foo=\"bar\"/></app></response>"; + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + + string eol_date; + EXPECT_TRUE(FakeSystemState::Get()->prefs()->GetString(kPrefsOmahaEolDate, + &eol_date)); + EXPECT_EQ("200", eol_date); +} + +TEST_F(OmahaRequestActionTest, PersistEolMissingDateTest) { + tuc_params_.http_response = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " + "protocol=\"3.0\"><app appid=\"test-app-id\" status=\"ok\">" + "<ping status=\"ok\"/><updatecheck status=\"noupdate\" " + "_foo=\"bar\"/></app></response>"; + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + const string kDate = "123"; + FakeSystemState::Get()->prefs()->SetString(kPrefsOmahaEolDate, kDate); + + ASSERT_TRUE(TestUpdateCheck()); + + string eol_date; + EXPECT_TRUE(FakeSystemState::Get()->prefs()->GetString(kPrefsOmahaEolDate, + &eol_date)); + EXPECT_EQ(kDate, eol_date); +} + +TEST_F(OmahaRequestActionTest, PersistEolBadDateTest) { + tuc_params_.http_response = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " + "protocol=\"3.0\"><app appid=\"test-app-id\" status=\"ok\">" + "<ping status=\"ok\"/><updatecheck status=\"noupdate\" " + "_eol_date=\"bad\" foo=\"bar\"/></app></response>"; + tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable; + tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset; + + ASSERT_TRUE(TestUpdateCheck()); + + string eol_date; + EXPECT_TRUE(FakeSystemState::Get()->prefs()->GetString(kPrefsOmahaEolDate, + &eol_date)); + EXPECT_EQ(kEolDateInvalid, StringToEolDate(eol_date)); +} + +TEST_F(OmahaRequestActionDlcPingTest, StorePingReplyNoPing) { + OmahaRequestParams::AppParams app_param = {.name = dlc_id_}; + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId(dlc_id_), app_param}}); + + ASSERT_TRUE(TestUpdateCheck()); + + int64_t temp_int; + // If there was no ping, the metadata files shouldn't exist yet. + EXPECT_FALSE(fake_prefs_->GetInt64(active_key_, &temp_int)); + EXPECT_FALSE(fake_prefs_->GetInt64(last_active_key_, &temp_int)); + EXPECT_FALSE(fake_prefs_->GetInt64(last_rollcall_key_, &temp_int)); +} + +TEST_F(OmahaRequestActionDlcPingTest, StorePingReplyActiveTest) { + // Create Active value + fake_prefs_->SetInt64(active_key_, 0); + + OmahaRequestParams::AppParams app_param = { + .active_counting_type = OmahaRequestParams::kDateBased, + .name = dlc_id_, + .ping_active = 1, + .send_ping = true}; + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId(dlc_id_), app_param}}); + + int64_t temp_int; + string temp_str; + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(fake_prefs_->GetInt64(active_key_, &temp_int)); + EXPECT_EQ(temp_int, kPingInactiveValue); + EXPECT_TRUE(fake_prefs_->GetString(last_active_key_, &temp_str)); + EXPECT_EQ(temp_str, "4763"); + EXPECT_TRUE(fake_prefs_->GetString(last_rollcall_key_, &temp_str)); + EXPECT_EQ(temp_str, "4763"); +} + +TEST_F(OmahaRequestActionDlcPingTest, StorePingReplyInactiveTest) { + // Create Active value + fake_prefs_->SetInt64(active_key_, 0); + + OmahaRequestParams::AppParams app_param = { + .active_counting_type = OmahaRequestParams::kDateBased, + .name = dlc_id_, + .ping_active = 0, + .send_ping = true}; + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId(dlc_id_), app_param}}); + + // Set the previous active value to an older value than 4763. + fake_prefs_->SetString(last_active_key_, "555"); + + int64_t temp_int; + ASSERT_TRUE(TestUpdateCheck()); + EXPECT_TRUE(fake_prefs_->GetInt64(active_key_, &temp_int)); + EXPECT_EQ(temp_int, kPingInactiveValue); + string temp_str; + EXPECT_TRUE(fake_prefs_->GetString(last_active_key_, &temp_str)); + EXPECT_EQ(temp_str, "555"); + EXPECT_TRUE(fake_prefs_->GetString(last_rollcall_key_, &temp_str)); + EXPECT_EQ(temp_str, "4763"); +} + +TEST_F(OmahaRequestActionTest, OmahaResponseUpdateCanExcludeCheck) { + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}}); + fake_update_response_.dlc_app_update = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false)); + ASSERT_TRUE(TestUpdateCheck()); + ASSERT_TRUE(delegate_.omaha_response_); + const auto& packages = delegate_.omaha_response_->packages; + ASSERT_EQ(packages.size(), 2); + + EXPECT_FALSE(packages[0].can_exclude); + EXPECT_TRUE(packages[1].can_exclude); +} + +TEST_F(OmahaRequestActionTest, OmahaResponseInstallCannotExcludeCheck) { + request_params_.set_is_install(true); + request_params_.set_dlc_apps_params( + {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}}); + fake_update_response_.dlc_app_update = true; + tuc_params_.http_response = fake_update_response_.GetUpdateResponse(); + + EXPECT_CALL(mock_excluder_, IsExcluded(_)).WillRepeatedly(Return(false)); + ASSERT_TRUE(TestUpdateCheck()); + ASSERT_TRUE(delegate_.omaha_response_); + const auto& packages = delegate_.omaha_response_->packages; + ASSERT_EQ(packages.size(), 2); + + EXPECT_FALSE(packages[0].can_exclude); + EXPECT_FALSE(packages[1].can_exclude); +} + +} // namespace chromeos_update_engine diff --git a/cros/omaha_request_builder_xml.cc b/cros/omaha_request_builder_xml.cc new file mode 100644 index 00000000..6cd9ab80 --- /dev/null +++ b/cros/omaha_request_builder_xml.cc @@ -0,0 +1,462 @@ +// +// 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/cros/omaha_request_builder_xml.h" + +#include <inttypes.h> + +#include <string> + +#include <base/guid.h> +#include <base/logging.h> +#include <base/strings/string_number_conversions.h> +#include <base/strings/string_util.h> +#include <base/strings/stringprintf.h> +#include <base/time/time.h> + +#include "update_engine/common/constants.h" +#include "update_engine/common/system_state.h" +#include "update_engine/common/utils.h" +#include "update_engine/cros/omaha_request_params.h" + +using std::string; + +namespace chromeos_update_engine { + +const char kNoVersion[] = "0.0.0.0"; +const int kPingNeverPinged = -1; +const int kPingUnknownValue = -2; +const int kPingActiveValue = 1; +const int kPingInactiveValue = 0; + +bool XmlEncode(const string& input, string* output) { + if (std::find_if(input.begin(), input.end(), [](const char c) { + return c & 0x80; + }) != input.end()) { + LOG(WARNING) << "Invalid ASCII-7 string passed to the XML encoder:"; + utils::HexDumpString(input); + return false; + } + output->clear(); + // We need at least input.size() space in the output, but the code below will + // handle it if we need more. + output->reserve(input.size()); + for (char c : input) { + switch (c) { + case '\"': + output->append("""); + break; + case '\'': + output->append("'"); + break; + case '&': + output->append("&"); + break; + case '<': + output->append("<"); + break; + case '>': + output->append(">"); + break; + default: + output->push_back(c); + } + } + return true; +} + +string XmlEncodeWithDefault(const string& input, const string& default_value) { + string output; + if (XmlEncode(input, &output)) + return output; + return default_value; +} + +string OmahaRequestBuilderXml::GetPing() const { + // Returns an XML ping element attribute assignment with attribute + // |name| and value |ping_days| if |ping_days| has a value that needs + // to be sent, or an empty string otherwise. + auto GetPingAttribute = [](const char* name, int ping_days) -> string { + if (ping_days > 0 || ping_days == kPingNeverPinged) + return base::StringPrintf(" %s=\"%d\"", name, ping_days); + return ""; + }; + + string ping_active = GetPingAttribute("a", ping_active_days_); + string ping_roll_call = GetPingAttribute("r", ping_roll_call_days_); + if (!ping_active.empty() || !ping_roll_call.empty()) { + return base::StringPrintf(" <ping active=\"1\"%s%s></ping>\n", + ping_active.c_str(), + ping_roll_call.c_str()); + } + return ""; +} + +string OmahaRequestBuilderXml::GetPingDateBased( + const OmahaRequestParams::AppParams& app_params) const { + if (!app_params.send_ping) + return ""; + string ping_active = ""; + string ping_ad = ""; + if (app_params.ping_active == kPingActiveValue) { + ping_active = + base::StringPrintf(" active=\"%" PRId64 "\"", app_params.ping_active); + ping_ad = base::StringPrintf(" ad=\"%" PRId64 "\"", + app_params.ping_date_last_active); + } + + string ping_rd = base::StringPrintf(" rd=\"%" PRId64 "\"", + app_params.ping_date_last_rollcall); + + return base::StringPrintf(" <ping%s%s%s></ping>\n", + ping_active.c_str(), + ping_ad.c_str(), + ping_rd.c_str()); +} + +string OmahaRequestBuilderXml::GetAppBody(const OmahaAppData& app_data) const { + string app_body; + if (event_ == nullptr) { + if (app_data.app_params.send_ping) { + switch (app_data.app_params.active_counting_type) { + case OmahaRequestParams::kDayBased: + app_body = GetPing(); + break; + case OmahaRequestParams::kDateBased: + app_body = GetPingDateBased(app_data.app_params); + break; + default: + NOTREACHED(); + } + } + if (!ping_only_) { + if (!app_data.skip_update) { + const auto* params = SystemState::Get()->request_params(); + app_body += " <updatecheck"; + if (!params->target_version_prefix().empty()) { + app_body += base::StringPrintf( + " targetversionprefix=\"%s\"", + XmlEncodeWithDefault(params->target_version_prefix()).c_str()); + // Rollback requires target_version_prefix set. + if (params->rollback_allowed()) { + app_body += " rollback_allowed=\"true\""; + } + } + if (!params->lts_tag().empty()) { + app_body += base::StringPrintf( + " ltstag=\"%s\"", + XmlEncodeWithDefault(params->lts_tag()).c_str()); + } + app_body += "></updatecheck>\n"; + } + + // If this is the first update check after a reboot following a previous + // update, generate an event containing the previous version number. If + // the previous version preference file doesn't exist the event is still + // generated with a previous version of 0.0.0.0 -- this is relevant for + // older clients or new installs. The previous version event is not sent + // for ping-only requests because they come before the client has + // rebooted. The previous version event is also not sent if it was already + // sent for this new version with a previous updatecheck. + auto* prefs = SystemState::Get()->prefs(); + string prev_version; + if (!prefs->GetString(kPrefsPreviousVersion, &prev_version)) { + prev_version = kNoVersion; + } + // We only store a non-empty previous version value after a successful + // update in the previous boot. After reporting it back to the server, + // we clear the previous version value so it doesn't get reported again. + if (!prev_version.empty()) { + app_body += base::StringPrintf( + " <event eventtype=\"%d\" eventresult=\"%d\" " + "previousversion=\"%s\"></event>\n", + OmahaEvent::kTypeRebootedAfterUpdate, + OmahaEvent::kResultSuccess, + XmlEncodeWithDefault(prev_version, kNoVersion).c_str()); + LOG_IF(WARNING, !prefs->SetString(kPrefsPreviousVersion, "")) + << "Unable to reset the previous version."; + } + } + } else { + int event_result = event_->result; + // The error code is an optional attribute so append it only if the result + // is not success. + string error_code; + if (event_result != OmahaEvent::kResultSuccess) { + error_code = base::StringPrintf(" errorcode=\"%d\"", + static_cast<int>(event_->error_code)); + } else if (app_data.is_dlc && !app_data.app_params.updated) { + // On a |OmahaEvent::kResultSuccess|, if the event is for an update + // completion and the App is a DLC, send error for excluded DLCs as they + // did not update. + event_result = OmahaEvent::Result::kResultError; + error_code = base::StringPrintf( + " errorcode=\"%d\"", + static_cast<int>(ErrorCode::kPackageExcludedFromUpdate)); + } + app_body = base::StringPrintf( + " <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n", + event_->type, + event_result, + error_code.c_str()); + } + + return app_body; +} + +string OmahaRequestBuilderXml::GetCohortArg( + const string& arg_name, + const string& prefs_key, + const string& override_value) const { + string cohort_value; + if (!override_value.empty()) { + // |override_value| take precedence over pref value. + cohort_value = override_value; + } else { + // There's nothing wrong with not having a given cohort setting, so we check + // existence first to avoid the warning log message. + const auto* prefs = SystemState::Get()->prefs(); + if (!prefs->Exists(prefs_key)) + return ""; + if (!prefs->GetString(prefs_key, &cohort_value) || cohort_value.empty()) + return ""; + } + // 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) { + LOG(WARNING) << "The omaha cohort setting " << arg_name + << " has a too big value, which must be an error or an " + "attacker trying to inhibit updates."; + return ""; + } + + string escaped_xml_value; + if (!XmlEncode(cohort_value, &escaped_xml_value)) { + LOG(WARNING) << "The omaha cohort setting " << arg_name + << " is ASCII-7 invalid, ignoring it."; + return ""; + } + + return base::StringPrintf( + "%s=\"%s\" ", arg_name.c_str(), escaped_xml_value.c_str()); +} + +bool IsValidComponentID(const string& id) { + for (char c : id) { + if (!isalnum(c) && c != '-' && c != '_' && c != '.') + return false; + } + return true; +} + +string OmahaRequestBuilderXml::GetApp(const OmahaAppData& app_data) const { + string app_body = GetAppBody(app_data); + string app_versions; + const auto* params = SystemState::Get()->request_params(); + + // If we are downgrading to a more stable channel and we are allowed to do + // powerwash, then pass 0.0.0.0 as the version. This is needed to get the + // highest-versioned payload on the destination channel. + if (params->ShouldPowerwash()) { + LOG(INFO) << "Passing OS version as 0.0.0.0 as we are set to powerwash " + << "on downgrading to the version in the more stable channel"; + app_versions = "version=\"" + string(kNoVersion) + "\" from_version=\"" + + XmlEncodeWithDefault(app_data.version, kNoVersion) + "\" "; + } else { + app_versions = "version=\"" + + XmlEncodeWithDefault(app_data.version, kNoVersion) + "\" "; + } + + string download_channel = params->download_channel(); + string app_channels = + "track=\"" + XmlEncodeWithDefault(download_channel) + "\" "; + if (params->current_channel() != download_channel) { + app_channels += "from_track=\"" + + XmlEncodeWithDefault(params->current_channel()) + "\" "; + } + + string delta_okay_str = + params->delta_okay() && !params->is_install() ? "true" : "false"; + + // If install_date_days is not set (e.g. its value is -1 ), don't + // include the attribute. + string install_date_in_days_str = ""; + if (install_date_in_days_ >= 0) { + install_date_in_days_str = + base::StringPrintf("installdate=\"%d\" ", install_date_in_days_); + } + + string app_cohort_args; + string cohort_key = kPrefsOmahaCohort; + string cohortname_key = kPrefsOmahaCohortName; + string cohorthint_key = kPrefsOmahaCohortHint; + + // Override the cohort keys for DLC App IDs. + const auto& dlc_apps_params = params->dlc_apps_params(); + auto itr = dlc_apps_params.find(app_data.id); + if (itr != dlc_apps_params.end()) { + auto dlc_id = itr->second.name; + const auto* prefs = SystemState::Get()->prefs(); + cohort_key = + prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsOmahaCohort}); + cohortname_key = + prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsOmahaCohortName}); + cohorthint_key = + prefs->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsOmahaCohortHint}); + } + + app_cohort_args += GetCohortArg("cohort", cohort_key); + app_cohort_args += GetCohortArg("cohortname", cohortname_key); + // Policy provided value overrides pref. + app_cohort_args += + GetCohortArg("cohorthint", + cohorthint_key, + params->autoupdate_token() /* override_value */); + + string fingerprint_arg; + if (!params->os_build_fingerprint().empty()) { + fingerprint_arg = "fingerprint=\"" + + XmlEncodeWithDefault(params->os_build_fingerprint()) + + "\" "; + } + + string buildtype_arg; + if (!params->os_build_type().empty()) { + buildtype_arg = "os_build_type=\"" + + XmlEncodeWithDefault(params->os_build_type()) + "\" "; + } + + string product_components_args; + if (!params->ShouldPowerwash() && !app_data.product_components.empty()) { + brillo::KeyValueStore store; + if (store.LoadFromString(app_data.product_components)) { + for (const string& key : store.GetKeys()) { + if (!IsValidComponentID(key)) { + LOG(ERROR) << "Invalid component id: " << key; + continue; + } + string version; + if (!store.GetString(key, &version)) { + LOG(ERROR) << "Failed to get version for " << key + << " in product_components."; + continue; + } + product_components_args += + base::StringPrintf("_%s.version=\"%s\" ", + key.c_str(), + XmlEncodeWithDefault(version).c_str()); + } + } else { + LOG(ERROR) << "Failed to parse product_components:\n" + << app_data.product_components; + } + } + + string requisition_arg; + if (!params->device_requisition().empty()) { + requisition_arg = "requisition=\"" + + XmlEncodeWithDefault(params->device_requisition()) + + "\" "; + } + + // clang-format off + string app_xml = " <app " + "appid=\"" + XmlEncodeWithDefault(app_data.id) + "\" " + + app_cohort_args + + app_versions + + app_channels + + product_components_args + + fingerprint_arg + + buildtype_arg + + "board=\"" + XmlEncodeWithDefault(params->os_board()) + "\" " + + "hardware_class=\"" + XmlEncodeWithDefault(params->hwid()) + "\" " + + "delta_okay=\"" + delta_okay_str + "\" " + + install_date_in_days_str + + + // DLC excluded for installs and updates. + (app_data.is_dlc ? "" : + "lang=\"" + XmlEncodeWithDefault(params->app_lang(), "en-US") + "\" " + + requisition_arg) + + + ">\n" + + app_body + + " </app>\n"; + // clang-format on + return app_xml; +} + +string OmahaRequestBuilderXml::GetOs() const { + const auto* params = SystemState::Get()->request_params(); + string os_xml = + " <os " + "version=\"" + + XmlEncodeWithDefault(params->os_version()) + "\" " + "platform=\"" + + XmlEncodeWithDefault(params->os_platform()) + "\" " + "sp=\"" + + XmlEncodeWithDefault(params->os_sp()) + + "\">" + "</os>\n"; + return os_xml; +} + +string OmahaRequestBuilderXml::GetRequest() const { + const auto* params = SystemState::Get()->request_params(); + string os_xml = GetOs(); + string app_xml = GetApps(); + + string request_xml = base::StringPrintf( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<request requestid=\"%s\" sessionid=\"%s\"" + " protocol=\"3.0\" updater=\"%s\" updaterversion=\"%s\"" + " installsource=\"%s\" ismachine=\"1\">\n%s%s</request>\n", + base::GenerateGUID().c_str() /* requestid */, + session_id_.c_str(), + constants::kOmahaUpdaterID, + kOmahaUpdaterVersion, + params->interactive() ? "ondemandupdate" : "scheduler", + os_xml.c_str(), + app_xml.c_str()); + + return request_xml; +} + +string OmahaRequestBuilderXml::GetApps() const { + const auto* params = SystemState::Get()->request_params(); + string app_xml = ""; + OmahaAppData product_app = { + .id = params->GetAppId(), + .version = params->app_version(), + .product_components = params->product_components(), + // Skips updatecheck for platform app in case of an install operation. + .skip_update = params->is_install(), + .is_dlc = false, + + .app_params = {.active_counting_type = OmahaRequestParams::kDayBased, + .send_ping = include_ping_}}; + app_xml += GetApp(product_app); + for (const auto& it : params->dlc_apps_params()) { + OmahaAppData dlc_app_data = { + .id = it.first, + .version = params->is_install() ? kNoVersion : params->app_version(), + .skip_update = false, + .is_dlc = true, + .app_params = it.second}; + app_xml += GetApp(dlc_app_data); + } + return app_xml; +} + +} // namespace chromeos_update_engine diff --git a/cros/omaha_request_builder_xml.h b/cros/omaha_request_builder_xml.h new file mode 100644 index 00000000..7c246f7f --- /dev/null +++ b/cros/omaha_request_builder_xml.h @@ -0,0 +1,192 @@ +// +// 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_CROS_OMAHA_REQUEST_BUILDER_XML_H_ +#define UPDATE_ENGINE_CROS_OMAHA_REQUEST_BUILDER_XML_H_ + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include <gtest/gtest_prod.h> // for FRIEND_TEST + +#include <brillo/secure_blob.h> +#include <curl/curl.h> + +#include "update_engine/common/action.h" +#include "update_engine/common/http_fetcher.h" +#include "update_engine/cros/omaha_request_params.h" +#include "update_engine/cros/omaha_response.h" + +namespace chromeos_update_engine { + +extern const char kNoVersion[]; +extern const int kPingNeverPinged; +extern const int kPingUnknownValue; +extern const int kPingActiveValue; +extern const int kPingInactiveValue; + +// This struct encapsulates the Omaha event information. For a +// complete list of defined event types and results, see +// http://code.google.com/p/omaha/wiki/ServerProtocol#event +struct OmahaEvent { + // The Type values correspond to EVENT_TYPE values of Omaha. + enum Type { + kTypeUnknown = 0, + kTypeDownloadComplete = 1, + kTypeInstallComplete = 2, + kTypeUpdateComplete = 3, + kTypeUpdateDownloadStarted = 13, + kTypeUpdateDownloadFinished = 14, + // Chromium OS reserved type sent after the first reboot following an update + // completed. + kTypeRebootedAfterUpdate = 54, + }; + + // The Result values correspond to EVENT_RESULT values of Omaha. + enum Result { + kResultError = 0, + kResultSuccess = 1, + kResultUpdateDeferred = 9, // When we ignore/defer updates due to policy. + }; + + OmahaEvent() + : type(kTypeUnknown), + result(kResultError), + error_code(ErrorCode::kError) {} + explicit OmahaEvent(Type in_type) + : type(in_type), + result(kResultSuccess), + error_code(ErrorCode::kSuccess) {} + OmahaEvent(Type in_type, Result in_result, ErrorCode in_error_code) + : type(in_type), result(in_result), error_code(in_error_code) {} + + Type type; + Result result; + ErrorCode error_code; +}; + +struct OmahaAppData { + std::string id; + std::string version; + std::string product_components; + bool skip_update; + bool is_dlc; + OmahaRequestParams::AppParams app_params; +}; + +// Encodes XML entities in a given string. Input must be ASCII-7 valid. If +// the input is invalid, the default value is used instead. +std::string XmlEncodeWithDefault(const std::string& input, + const std::string& default_value = ""); + +// Escapes text so it can be included as character data and attribute +// values. The |input| string must be valid ASCII-7, no UTF-8 supported. +// Returns whether the |input| was valid and escaped properly in |output|. +bool XmlEncode(const std::string& input, std::string* output); + +// Returns a boolean based on examining each character on whether it's a valid +// component (meaning all characters are an alphanum excluding '-', '_', '.'). +bool IsValidComponentID(const std::string& id); + +class OmahaRequestBuilder { + public: + OmahaRequestBuilder() = default; + virtual ~OmahaRequestBuilder() = default; + + virtual std::string GetRequest() const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(OmahaRequestBuilder); +}; + +class OmahaRequestBuilderXml : OmahaRequestBuilder { + public: + OmahaRequestBuilderXml(const OmahaEvent* event, + bool ping_only, + bool include_ping, + int ping_active_days, + int ping_roll_call_days, + int install_date_in_days, + const std::string& session_id) + : event_(event), + ping_only_(ping_only), + include_ping_(include_ping), + ping_active_days_(ping_active_days), + ping_roll_call_days_(ping_roll_call_days), + install_date_in_days_(install_date_in_days), + session_id_(session_id) {} + + ~OmahaRequestBuilderXml() override = default; + + // Returns an XML that corresponds to the entire Omaha request. + std::string GetRequest() const override; + + private: + FRIEND_TEST(OmahaRequestBuilderXmlTest, PlatformGetAppTest); + FRIEND_TEST(OmahaRequestBuilderXmlTest, DlcGetAppTest); + + // Returns an XML that corresponds to the entire <os> node of the Omaha + // request based on the member variables. + std::string GetOs() const; + + // Returns an XML that corresponds to all <app> nodes of the Omaha + // request based on the given parameters. + std::string GetApps() const; + + // Returns an XML that corresponds to the single <app> node of the Omaha + // request based on the given parameters. + std::string GetApp(const OmahaAppData& app_data) const; + + // Returns an XML that goes into the body of the <app> element of the Omaha + // request based on the given parameters. + std::string GetAppBody(const OmahaAppData& app_data) const; + + // Returns the cohort* argument to include in the <app> tag for the passed + // |arg_name| and |prefs_key|, if any. The return value is suitable to + // concatenate to the list of arguments and includes a space at the end. + std::string GetCohortArg(const std::string& arg_name, + const std::string& prefs_key, + const std::string& override_value = "") const; + + // Returns an XML ping element if any of the elapsed days need to be + // sent, or an empty string otherwise. + std::string GetPing() const; + + // Returns an XML ping element if any of the elapsed days need to be + // sent, or an empty string otherwise. + std::string GetPingDateBased( + const OmahaRequestParams::AppParams& app_params) const; + + const OmahaEvent* event_; + bool ping_only_; + bool include_ping_; + int ping_active_days_; + int ping_roll_call_days_; + int install_date_in_days_; + std::string session_id_; + + DISALLOW_COPY_AND_ASSIGN(OmahaRequestBuilderXml); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_CROS_OMAHA_REQUEST_BUILDER_XML_H_ diff --git a/cros/omaha_request_builder_xml_unittest.cc b/cros/omaha_request_builder_xml_unittest.cc new file mode 100644 index 00000000..76a72411 --- /dev/null +++ b/cros/omaha_request_builder_xml_unittest.cc @@ -0,0 +1,421 @@ +// +// 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/cros/omaha_request_builder_xml.h" + +#include <string> +#include <utility> +#include <vector> + +#include <base/guid.h> +#include <base/strings/stringprintf.h> +#include <gtest/gtest.h> + +#include "update_engine/cros/fake_system_state.h" + +using std::pair; +using std::string; +using std::vector; +using testing::_; +using testing::DoAll; +using testing::Return; +using testing::SetArgPointee; + +namespace chromeos_update_engine { + +namespace { +// Helper to find key and extract value from the given string |xml|, instead +// of using a full parser. The attribute key will be followed by "=\"" as xml +// attribute values must be within double quotes (not single quotes). +static string FindAttributeKeyValueInXml(const string& xml, + const string& key, + const size_t val_size) { + string key_with_quotes = key + "=\""; + const size_t val_start_pos = xml.find(key); + if (val_start_pos == string::npos) + return ""; + return xml.substr(val_start_pos + key_with_quotes.size(), val_size); +} +// Helper to find the count of substring in a string. +static size_t CountSubstringInString(const string& str, const string& substr) { + size_t count = 0, pos = 0; + while ((pos = str.find(substr, pos ? pos + 1 : 0)) != string::npos) + ++count; + return count; +} +} // namespace + +class OmahaRequestBuilderXmlTest : public ::testing::Test { + protected: + void SetUp() override { + FakeSystemState::CreateInstance(); + FakeSystemState::Get()->set_request_params(¶ms_); + } + void TearDown() override {} + + static constexpr size_t kGuidSize = 36; + + OmahaRequestParams params_; +}; + +TEST_F(OmahaRequestBuilderXmlTest, XmlEncodeTest) { + string output; + vector<pair<string, string>> xml_encode_pairs = { + {"ab", "ab"}, + {"a<b", "a<b"}, + {"<&>\"\'\\", "<&>"'\\"}, + {"<&>", "&lt;&amp;&gt;"}}; + for (const auto& xml_encode_pair : xml_encode_pairs) { + const auto& before_encoding = xml_encode_pair.first; + const auto& after_encoding = xml_encode_pair.second; + EXPECT_TRUE(XmlEncode(before_encoding, &output)); + EXPECT_EQ(after_encoding, output); + } + // Check that unterminated UTF-8 strings are handled properly. + EXPECT_FALSE(XmlEncode("\xc2", &output)); + // Fail with invalid ASCII-7 chars. + EXPECT_FALSE(XmlEncode("This is an 'n' with a tilde: \xc3\xb1", &output)); +} + +TEST_F(OmahaRequestBuilderXmlTest, XmlEncodeWithDefaultTest) { + EXPECT_EQ("", XmlEncodeWithDefault("")); + EXPECT_EQ("<&>", XmlEncodeWithDefault("<&>", "something else")); + EXPECT_EQ("<not escaped>", XmlEncodeWithDefault("\xc2", "<not escaped>")); +} + +TEST_F(OmahaRequestBuilderXmlTest, PlatformGetAppTest) { + params_.set_device_requisition("device requisition"); + OmahaRequestBuilderXml omaha_request{nullptr, + false, + false, + 0, + 0, + 0, + ""}; + OmahaAppData dlc_app_data = {.id = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", + .version = "", + .skip_update = false, + .is_dlc = false}; + + // Verify that the attributes that shouldn't be missing for Platform AppID are + // in fact present in the <app ...></app>. + const string app = omaha_request.GetApp(dlc_app_data); + EXPECT_NE(string::npos, app.find("lang=")); + EXPECT_NE(string::npos, app.find("requisition=")); +} + +TEST_F(OmahaRequestBuilderXmlTest, DlcGetAppTest) { + params_.set_device_requisition("device requisition"); + OmahaRequestBuilderXml omaha_request{nullptr, + false, + false, + 0, + 0, + 0, + ""}; + OmahaAppData dlc_app_data = { + .id = "_dlc_id", .version = "", .skip_update = false, .is_dlc = true}; + + // Verify that the attributes that should be missing for DLC AppIDs are in + // fact not present in the <app ...></app>. + const string app = omaha_request.GetApp(dlc_app_data); + EXPECT_EQ(string::npos, app.find("lang=")); + EXPECT_EQ(string::npos, app.find("requisition=")); +} + +TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlRequestIdTest) { + OmahaRequestBuilderXml omaha_request{nullptr, + false, + false, + 0, + 0, + 0, + ""}; + const string request_xml = omaha_request.GetRequest(); + const string key = "requestid"; + const string request_id = + FindAttributeKeyValueInXml(request_xml, key, kGuidSize); + // A valid |request_id| is either a GUID version 4 or empty string. + if (!request_id.empty()) + EXPECT_TRUE(base::IsValidGUID(request_id)); +} + +TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlSessionIdTest) { + const string gen_session_id = base::GenerateGUID(); + OmahaRequestBuilderXml omaha_request{nullptr, + false, + false, + 0, + 0, + 0, + gen_session_id}; + const string request_xml = omaha_request.GetRequest(); + const string key = "sessionid"; + const string session_id = + FindAttributeKeyValueInXml(request_xml, key, kGuidSize); + // A valid |session_id| is either a GUID version 4 or empty string. + if (!session_id.empty()) { + EXPECT_TRUE(base::IsValidGUID(session_id)); + } + EXPECT_EQ(gen_session_id, session_id); +} + +TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlPlatformUpdateTest) { + OmahaRequestBuilderXml omaha_request{nullptr, + false, + false, + 0, + 0, + 0, + ""}; + const string request_xml = omaha_request.GetRequest(); + EXPECT_EQ(1, CountSubstringInString(request_xml, "<updatecheck")) + << request_xml; +} + +TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlPlatformUpdateWithDlcsTest) { + params_.set_dlc_apps_params( + {{params_.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}}, + {params_.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}}); + OmahaRequestBuilderXml omaha_request{nullptr, + false, + false, + 0, + 0, + 0, + ""}; + const string request_xml = omaha_request.GetRequest(); + EXPECT_EQ(3, CountSubstringInString(request_xml, "<updatecheck")) + << request_xml; +} + +TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcInstallationTest) { + const std::map<std::string, OmahaRequestParams::AppParams> dlcs = { + {params_.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}}, + {params_.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}}; + params_.set_dlc_apps_params(dlcs); + params_.set_is_install(true); + OmahaRequestBuilderXml omaha_request{nullptr, + false, + false, + 0, + 0, + 0, + ""}; + const string request_xml = omaha_request.GetRequest(); + EXPECT_EQ(2, CountSubstringInString(request_xml, "<updatecheck")) + << request_xml; + + auto FindAppId = [request_xml](size_t pos) -> size_t { + return request_xml.find("<app appid", pos); + }; + // Skip over the Platform AppID, which is always first. + size_t pos = FindAppId(0); + for (auto&& _ : dlcs) { + (void)_; + EXPECT_NE(string::npos, (pos = FindAppId(pos + 1))) << request_xml; + const string dlc_app_id_version = FindAttributeKeyValueInXml( + request_xml.substr(pos), "version", string(kNoVersion).size()); + EXPECT_EQ(kNoVersion, dlc_app_id_version); + + const string false_str = "false"; + const string dlc_app_id_delta_okay = FindAttributeKeyValueInXml( + request_xml.substr(pos), "delta_okay", false_str.length()); + EXPECT_EQ(false_str, dlc_app_id_delta_okay); + } +} + +TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcNoPing) { + params_.set_dlc_apps_params( + {{params_.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}}}); + OmahaRequestBuilderXml omaha_request{nullptr, + false, + false, + 0, + 0, + 0, + ""}; + const string request_xml = omaha_request.GetRequest(); + EXPECT_EQ(0, CountSubstringInString(request_xml, "<ping")) << request_xml; +} + +TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcPingRollCallNoActive) { + params_.set_dlc_apps_params( + {{params_.GetDlcAppId("dlc_no_0"), + {.active_counting_type = OmahaRequestParams::kDateBased, + .name = "dlc_no_0", + .ping_date_last_active = 25, + .ping_date_last_rollcall = 36, + .send_ping = true}}}); + OmahaRequestBuilderXml omaha_request{nullptr, + false, + false, + 0, + 0, + 0, + ""}; + const string request_xml = omaha_request.GetRequest(); + EXPECT_EQ(1, CountSubstringInString(request_xml, "<ping rd=\"36\"")) + << request_xml; +} + +TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcPingRollCallAndActive) { + params_.set_dlc_apps_params( + {{params_.GetDlcAppId("dlc_no_0"), + {.active_counting_type = OmahaRequestParams::kDateBased, + .name = "dlc_no_0", + .ping_active = 1, + .ping_date_last_active = 25, + .ping_date_last_rollcall = 36, + .send_ping = true}}}); + OmahaRequestBuilderXml omaha_request{nullptr, + false, + false, + 0, + 0, + 0, + ""}; + const string request_xml = omaha_request.GetRequest(); + EXPECT_EQ(1, + CountSubstringInString(request_xml, + "<ping active=\"1\" ad=\"25\" rd=\"36\"")) + << request_xml; +} + +TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlUpdateCompleteEvent) { + OmahaEvent event(OmahaEvent::kTypeUpdateComplete); + OmahaRequestBuilderXml omaha_request{&event, + false, + false, + 0, + 0, + 0, + ""}; + const string request_xml = omaha_request.GetRequest(); + LOG(INFO) << request_xml; + EXPECT_EQ( + 1, + CountSubstringInString( + request_xml, "<event eventtype=\"3\" eventresult=\"1\"></event>")) + << request_xml; +} + +TEST_F(OmahaRequestBuilderXmlTest, + GetRequestXmlUpdateCompleteEventSomeDlcsExcluded) { + params_.set_dlc_apps_params({ + {params_.GetDlcAppId("dlc_1"), {.updated = true}}, + {params_.GetDlcAppId("dlc_2"), {.updated = false}}, + }); + OmahaEvent event(OmahaEvent::kTypeUpdateComplete); + OmahaRequestBuilderXml omaha_request{&event, + false, + false, + 0, + 0, + 0, + ""}; + const string request_xml = omaha_request.GetRequest(); + EXPECT_EQ( + 2, + CountSubstringInString( + request_xml, "<event eventtype=\"3\" eventresult=\"1\"></event>")) + << request_xml; + EXPECT_EQ( + 1, + CountSubstringInString( + request_xml, + "<event eventtype=\"3\" eventresult=\"0\" errorcode=\"62\"></event>")) + << request_xml; +} + +TEST_F(OmahaRequestBuilderXmlTest, + GetRequestXmlUpdateCompleteEventAllDlcsExcluded) { + params_.set_dlc_apps_params({ + {params_.GetDlcAppId("dlc_1"), {.updated = false}}, + {params_.GetDlcAppId("dlc_2"), {.updated = false}}, + }); + OmahaEvent event(OmahaEvent::kTypeUpdateComplete); + OmahaRequestBuilderXml omaha_request{&event, + false, + false, + 0, + 0, + 0, + ""}; + const string request_xml = omaha_request.GetRequest(); + EXPECT_EQ( + 1, + CountSubstringInString( + request_xml, "<event eventtype=\"3\" eventresult=\"1\"></event>")) + << request_xml; + EXPECT_EQ( + 2, + CountSubstringInString( + request_xml, + "<event eventtype=\"3\" eventresult=\"0\" errorcode=\"62\"></event>")) + << request_xml; +} + +TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcCohortMissingCheck) { + constexpr char kDlcId[] = "test-dlc-id"; + params_.set_dlc_apps_params( + {{params_.GetDlcAppId(kDlcId), {.name = kDlcId}}}); + OmahaEvent event(OmahaEvent::kTypeUpdateDownloadStarted); + OmahaRequestBuilderXml omaha_request{&event, false, false, 0, 0, 0, ""}; + const string request_xml = omaha_request.GetRequest(); + + // Check that no cohorts are in the request. + EXPECT_EQ(0, CountSubstringInString(request_xml, "cohort=")) << request_xml; + EXPECT_EQ(0, CountSubstringInString(request_xml, "cohortname=")) + << request_xml; + EXPECT_EQ(0, CountSubstringInString(request_xml, "cohorthint=")) + << request_xml; +} + +TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcCohortCheck) { + const string kDlcId = "test-dlc-id"; + params_.set_dlc_apps_params( + {{params_.GetDlcAppId(kDlcId), {.name = kDlcId}}}); + auto* fake_prefs = FakeSystemState::Get()->fake_prefs(); + OmahaEvent event(OmahaEvent::kTypeUpdateDownloadStarted); + OmahaRequestBuilderXml omaha_request{&event, false, false, 0, 0, 0, ""}; + // DLC App ID Expectations. + const string dlc_cohort_key = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, kDlcId, kPrefsOmahaCohort}); + const string kDlcCohortVal = "test-cohort"; + EXPECT_TRUE(fake_prefs->SetString(dlc_cohort_key, kDlcCohortVal)); + const string dlc_cohort_name_key = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, kDlcId, kPrefsOmahaCohortName}); + const string kDlcCohortNameVal = "test-cohortname"; + EXPECT_TRUE(fake_prefs->SetString(dlc_cohort_name_key, kDlcCohortNameVal)); + const string dlc_cohort_hint_key = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, kDlcId, kPrefsOmahaCohortHint}); + const string kDlcCohortHintVal = "test-cohortval"; + EXPECT_TRUE(fake_prefs->SetString(dlc_cohort_hint_key, kDlcCohortHintVal)); + const string request_xml = omaha_request.GetRequest(); + + EXPECT_EQ(1, + CountSubstringInString( + request_xml, + base::StringPrintf( + "cohort=\"%s\" cohortname=\"%s\" cohorthint=\"%s\"", + kDlcCohortVal.c_str(), + kDlcCohortNameVal.c_str(), + kDlcCohortHintVal.c_str()))) + << request_xml; +} + +} // namespace chromeos_update_engine diff --git a/omaha_request_params.cc b/cros/omaha_request_params.cc index 8c410f17..adcfc759 100644 --- a/omaha_request_params.cc +++ b/cros/omaha_request_params.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/omaha_request_params.h" +#include "update_engine/cros/omaha_request_params.h" #include <errno.h> #include <fcntl.h> @@ -25,6 +25,7 @@ #include <vector> #include <base/files/file_util.h> +#include <base/stl_util.h> #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> #include <brillo/key_value_store.h> @@ -34,14 +35,14 @@ #include "update_engine/common/constants.h" #include "update_engine/common/hardware_interface.h" #include "update_engine/common/platform_constants.h" +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" -#include "update_engine/system_state.h" +#include "update_engine/update_manager/policy.h" #define CALL_MEMBER_FN(object, member) ((object).*(member)) -using std::map; +using chromeos_update_manager::UpdateCheckParams; using std::string; -using std::vector; namespace chromeos_update_engine { @@ -60,14 +61,14 @@ OmahaRequestParams::~OmahaRequestParams() { test::SetImagePropertiesRootPrefix(nullptr); } -bool OmahaRequestParams::Init(const string& in_app_version, - const string& in_update_url, - bool in_interactive) { +bool OmahaRequestParams::Init(const string& app_version, + const string& update_url, + const UpdateCheckParams& params) { LOG(INFO) << "Initializing parameters for this update attempt"; - image_props_ = LoadImageProperties(system_state_); - mutable_image_props_ = LoadMutableImageProperties(system_state_); + image_props_ = LoadImageProperties(); + mutable_image_props_ = LoadMutableImageProperties(); - // 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)) @@ -77,24 +78,14 @@ bool OmahaRequestParams::Init(const string& in_app_version, LOG(INFO) << "Running from channel " << image_props_.current_channel; os_platform_ = constants::kOmahaPlatformName; - if (!image_props_.system_version.empty()) { - if (in_app_version == "ForcedUpdate") { - image_props_.system_version = in_app_version; - } - os_version_ = image_props_.system_version; - } else { - os_version_ = OmahaRequestParams::kOsVersion; - } - if (!in_app_version.empty()) - image_props_.version = in_app_version; + os_version_ = OmahaRequestParams::kOsVersion; + if (!app_version.empty()) + image_props_.version = app_version; os_sp_ = image_props_.version + "_" + GetMachineType(); app_lang_ = "en-US"; - hwid_ = system_state_->hardware()->GetHardwareClass(); - if (CollectECFWVersions()) { - fw_version_ = system_state_->hardware()->GetFirmwareVersion(); - ec_version_ = system_state_->hardware()->GetECVersion(); - } + hwid_ = SystemState::Get()->hardware()->GetHardwareClass(); + device_requisition_ = SystemState::Get()->hardware()->GetDeviceRequisition(); if (image_props_.current_channel == mutable_image_props_.target_channel) { // deltas are only okay if the /.nodelta file does not exist. if we don't @@ -115,17 +106,53 @@ bool OmahaRequestParams::Init(const string& in_app_version, delta_okay_ = false; } - if (in_update_url.empty()) + if (update_url.empty()) update_url_ = image_props_.omaha_url; else - update_url_ = in_update_url; + update_url_ = update_url; // Set the interactive flag accordingly. - interactive_ = in_interactive; + interactive_ = params.interactive; - dlc_module_ids_.clear(); + dlc_apps_params_.clear(); // Set false so it will do update by default. is_install_ = false; + + target_version_prefix_ = params.target_version_prefix; + + lts_tag_ = params.lts_tag; + + autoupdate_token_ = params.quick_fix_build_token; + + rollback_allowed_ = params.rollback_allowed; + + // Set whether saving data over rollback is requested. + rollback_data_save_requested_ = params.rollback_data_save_requested; + + // Set how many milestones of rollback are allowed. + rollback_allowed_milestones_ = params.rollback_allowed_milestones; + + // Set the target channel, if one was provided. + if (params.target_channel.empty()) { + LOG(INFO) << "No target channel mandated by policy."; + } else { + LOG(INFO) << "Setting target channel as mandated: " + << params.target_channel; + string error_message; + if (!SetTargetChannel(params.target_channel, + params.rollback_on_channel_downgrade, + &error_message)) { + LOG(ERROR) << "Setting the channel failed: " << error_message; + } + + // Since this is the beginning of a new attempt, update the download + // channel. The download channel won't be updated until the next attempt, + // even if target channel changes meanwhile, so that how we'll know if we + // should cancel the current download attempt if there's such a change in + // target channel. + UpdateDownloadChannel(); + } + return true; } @@ -134,14 +161,6 @@ bool OmahaRequestParams::IsUpdateUrlOfficial() const { update_url_ == image_props_.omaha_url); } -bool OmahaRequestParams::CollectECFWVersions() const { - return base::StartsWith( - hwid_, string("PARROT"), base::CompareCase::SENSITIVE) || - base::StartsWith( - hwid_, string("SPRING"), base::CompareCase::SENSITIVE) || - base::StartsWith(hwid_, string("SNOW"), base::CompareCase::SENSITIVE); -} - bool OmahaRequestParams::SetTargetChannel(const string& new_target_channel, bool is_powerwash_allowed, string* error_message) { @@ -160,7 +179,7 @@ bool OmahaRequestParams::SetTargetChannel(const string& new_target_channel, new_props.target_channel = new_target_channel; new_props.is_powerwash_allowed = is_powerwash_allowed; - if (!StoreMutableImageProperties(system_state_, new_props)) { + if (!StoreMutableImageProperties(new_props)) { if (error_message) *error_message = "Error storing the new channel value."; return false; @@ -217,7 +236,7 @@ void OmahaRequestParams::set_root(const string& root) { } int OmahaRequestParams::GetChannelIndex(const string& channel) const { - for (size_t t = 0; t < arraysize(kChannelsByStability); ++t) + for (size_t t = 0; t < base::size(kChannelsByStability); ++t) if (channel == kChannelsByStability[t]) return t; @@ -247,4 +266,29 @@ string OmahaRequestParams::GetAppId() const { : image_props_.product_id; } +string OmahaRequestParams::GetDlcAppId(const std::string& dlc_id) const { + // Create APP ID according to |dlc_id| (sticking the current AppID to the + // DLC module ID with an underscode). + return GetAppId() + "_" + dlc_id; +} + +bool OmahaRequestParams::IsDlcAppId(const std::string& app_id) const { + return dlc_apps_params().find(app_id) != dlc_apps_params().end(); +} + +bool OmahaRequestParams::GetDlcId(const string& app_id, string* dlc_id) const { + auto itr = dlc_apps_params_.find(app_id); + if (itr == dlc_apps_params_.end()) + return false; + *dlc_id = itr->second.name; + return true; +} + +void OmahaRequestParams::SetDlcNoUpdate(const string& app_id) { + auto itr = dlc_apps_params_.find(app_id); + if (itr == dlc_apps_params_.end()) + return; + itr->second.updated = false; +} + } // namespace chromeos_update_engine diff --git a/omaha_request_params.h b/cros/omaha_request_params.h index 18235c04..fd4c2e23 100644 --- a/omaha_request_params.h +++ b/cros/omaha_request_params.h @@ -14,11 +14,12 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_ -#define UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_ +#ifndef UPDATE_ENGINE_CROS_OMAHA_REQUEST_PARAMS_H_ +#define UPDATE_ENGINE_CROS_OMAHA_REQUEST_PARAMS_H_ #include <stdint.h> +#include <map> #include <string> #include <vector> @@ -26,16 +27,16 @@ #include <base/time/time.h> #include <gtest/gtest_prod.h> // for FRIEND_TEST +#include "update_engine/common/constants.h" #include "update_engine/common/platform_constants.h" -#include "update_engine/image_properties.h" +#include "update_engine/cros/image_properties.h" +#include "update_engine/update_manager/policy.h" // This gathers local system information and prepares info used by the // Omaha request action. namespace chromeos_update_engine { -class SystemState; - // This class encapsulates the data Omaha gets for the request, along with // essential state needed for the processing of the request/response. The // strings in this struct should not be XML escaped. @@ -44,13 +45,13 @@ class SystemState; // reflect its lifetime more appropriately. class OmahaRequestParams { public: - explicit OmahaRequestParams(SystemState* system_state) - : system_state_(system_state), - os_platform_(constants::kOmahaPlatformName), + OmahaRequestParams() + : os_platform_(constants::kOmahaPlatformName), os_version_(kOsVersion), delta_okay_(true), interactive_(false), rollback_allowed_(false), + rollback_data_save_requested_(false), wall_clock_based_wait_enabled_(false), update_check_count_wait_enabled_(false), min_update_checks_needed_(kDefaultMinUpdateChecks), @@ -59,6 +60,24 @@ class OmahaRequestParams { virtual ~OmahaRequestParams(); + enum ActiveCountingType { + kDayBased = 0, + kDateBased, + }; + + struct AppParams { + ActiveCountingType active_counting_type; + // |name| is only used for DLCs to store the DLC ID. + std::string name; + int64_t ping_active; + int64_t ping_date_last_active; + int64_t ping_date_last_rollcall; + bool send_ping; + // |updated| is only used for DLCs to decide sending DBus message to + // dlcservice on an install/update completion. + bool updated = true; + }; + // Setters and getters for the various properties. inline std::string os_platform() const { return os_platform_; } inline std::string os_version() const { return os_version_; } @@ -72,26 +91,18 @@ class OmahaRequestParams { inline std::string canary_app_id() const { return image_props_.canary_product_id; } - inline std::string system_app_id() const { return image_props_.system_id; } - inline void set_system_app_id(const std::string& system_app_id) { - image_props_.system_id = system_app_id; - } inline void set_app_id(const std::string& app_id) { image_props_.product_id = app_id; image_props_.canary_product_id = app_id; } inline std::string app_lang() const { return app_lang_; } inline std::string hwid() const { return hwid_; } - inline std::string fw_version() const { return fw_version_; } - inline std::string ec_version() const { return ec_version_; } + inline std::string device_requisition() const { return device_requisition_; } inline void set_app_version(const std::string& version) { image_props_.version = version; } inline std::string app_version() const { return image_props_.version; } - inline std::string system_version() const { - return image_props_.system_version; - } inline std::string product_components() const { return image_props_.product_components; } @@ -126,12 +137,33 @@ class OmahaRequestParams { return target_version_prefix_; } + inline std::string lts_tag() const { return lts_tag_; } + + inline void set_lts_tag(const std::string& hint) { lts_tag_ = hint; } + inline void set_rollback_allowed(bool rollback_allowed) { rollback_allowed_ = rollback_allowed; } inline bool rollback_allowed() const { return rollback_allowed_; } + inline void set_rollback_data_save_requested( + bool rollback_data_save_requested) { + rollback_data_save_requested_ = rollback_data_save_requested; + } + + inline bool rollback_data_save_requested() const { + return rollback_data_save_requested_; + } + + inline void set_rollback_allowed_milestones(int rollback_allowed_milestones) { + rollback_allowed_milestones_ = rollback_allowed_milestones; + } + + inline int rollback_allowed_milestones() const { + return rollback_allowed_milestones_; + } + inline void set_wall_clock_based_wait_enabled(bool enabled) { wall_clock_based_wait_enabled_ = enabled; } @@ -165,20 +197,41 @@ class OmahaRequestParams { inline int64_t max_update_checks_allowed() const { return max_update_checks_allowed_; } - inline void set_dlc_module_ids( - const std::vector<std::string>& dlc_module_ids) { - dlc_module_ids_ = dlc_module_ids; + inline void set_dlc_apps_params( + const std::map<std::string, AppParams>& dlc_apps_params) { + dlc_apps_params_ = dlc_apps_params; } - inline std::vector<std::string> dlc_module_ids() const { - return dlc_module_ids_; + inline const std::map<std::string, AppParams>& dlc_apps_params() const { + return dlc_apps_params_; } inline void set_is_install(bool is_install) { is_install_ = is_install; } inline bool is_install() const { return is_install_; } - // Returns the app id corresponding to the current value of the + inline void set_autoupdate_token(const std::string& token) { + autoupdate_token_ = token; + } + inline const std::string& autoupdate_token() const { + return autoupdate_token_; + } + + // Returns the App ID corresponding to the current value of the // download channel. virtual std::string GetAppId() const; + // Returns the DLC app ID. + virtual std::string GetDlcAppId(const std::string& dlc_id) const; + + // Returns true if the App ID is a DLC App ID that is currently part of the + // request parameters. + virtual bool IsDlcAppId(const std::string& app_id) const; + + // Returns the DLC App ID if the given App ID is a DLC that is currently part + // of the request parameters. + virtual bool GetDlcId(const std::string& app_id, std::string* dlc_id) const; + + // If the App ID is a DLC App ID will set to no update. + void SetDlcNoUpdate(const std::string& app_id); + // Suggested defaults static const char kOsVersion[]; static const int64_t kDefaultMinUpdateChecks = 0; @@ -189,7 +242,7 @@ class OmahaRequestParams { // of the parameter. Returns true on success, false otherwise. bool Init(const std::string& in_app_version, const std::string& in_update_url, - bool in_interactive); + const chromeos_update_manager::UpdateCheckParams& params); // Permanently changes the release channel to |channel|. Performs a // powerwash, if required and allowed. @@ -210,8 +263,10 @@ class OmahaRequestParams { // or Init is called again. virtual void UpdateDownloadChannel(); - // Returns whether we should powerwash for this update. - virtual bool ShouldPowerwash() const; + // Returns whether we should powerwash for this update. Note that this is + // just an indication, the final decision to powerwash or not is made in the + // response handler. + bool ShouldPowerwash() const; // Check if the provided update URL is official, meaning either the default // autoupdate server or the autoupdate autotest server. @@ -231,19 +286,19 @@ class OmahaRequestParams { } void set_app_lang(const std::string& app_lang) { app_lang_ = app_lang; } void set_hwid(const std::string& hwid) { hwid_ = hwid; } - void set_fw_version(const std::string& fw_version) { - fw_version_ = fw_version; - } - void set_ec_version(const std::string& ec_version) { - ec_version_ = ec_version; - } void set_is_powerwash_allowed(bool powerwash_allowed) { mutable_image_props_.is_powerwash_allowed = powerwash_allowed; } + bool is_powerwash_allowed() { + return mutable_image_props_.is_powerwash_allowed; + } + + void set_device_requisition(const std::string& requisition) { + device_requisition_ = requisition; + } private: FRIEND_TEST(OmahaRequestParamsTest, ChannelIndexTest); - FRIEND_TEST(OmahaRequestParamsTest, CollectECFWVersionsTest); FRIEND_TEST(OmahaRequestParamsTest, IsValidChannelTest); FRIEND_TEST(OmahaRequestParamsTest, SetIsPowerwashAllowedTest); FRIEND_TEST(OmahaRequestParamsTest, SetTargetChannelInvalidTest); @@ -266,16 +321,9 @@ class OmahaRequestParams { // i.e. index(target_channel) > index(current_channel). bool ToMoreStableChannel() const; - // Returns True if we should store the fw/ec versions based on our hwid_. - // Compares hwid to a set of whitelisted prefixes. - bool CollectECFWVersions() const; - // Gets the machine type (e.g. "i686"). std::string GetMachineType() const; - // Global system context. - SystemState* system_state_; - // The system image properties. ImageProperties image_props_; MutableImageProperties mutable_image_props_; @@ -306,11 +354,15 @@ class OmahaRequestParams { // changed and cancel the current download attempt. std::string download_channel_; - std::string hwid_; // Hardware Qualification ID of the client - std::string fw_version_; // Chrome OS Firmware Version. - std::string ec_version_; // Chrome OS EC Version. - bool delta_okay_; // If this client can accept a delta - bool interactive_; // Whether this is a user-initiated update check + // The value defining the parameters of the LTS (Long Term Support). + std::string lts_tag_; + + std::string hwid_; // Hardware Qualification ID of the client + // TODO(b:133324571) tracks removal of this field once it is no longer + // needed in AU requests. Remove by October 1st 2019. + std::string device_requisition_; // Chrome OS Requisition type. + bool delta_okay_; // If this client can accept a delta + bool interactive_; // Whether this is a user-initiated update check // The URL to send the Omaha request to. std::string update_url_; @@ -322,6 +374,12 @@ class OmahaRequestParams { // Whether the client is accepting rollback images too. bool rollback_allowed_; + // Whether rollbacks should preserve some system state during powerwash. + bool rollback_data_save_requested_; + + // How many milestones the client can rollback to. + int rollback_allowed_milestones_; + // True if scattering or staging are enabled, in which case waiting_period_ // specifies the amount of absolute time that we've to wait for before sending // a request to Omaha. @@ -339,17 +397,22 @@ class OmahaRequestParams { // When reading files, prepend root_ to the paths. Useful for testing. std::string root_; - // A list of DLC module IDs to install. - std::vector<std::string> dlc_module_ids_; + // A list of DLC modules to install. A mapping from DLC App ID to |AppParams|. + std::map<std::string, AppParams> dlc_apps_params_; // This variable defines whether the payload is being installed in the current // partition. At the moment, this is used for installing DLC modules on the // current active partition instead of the inactive partition. bool is_install_; + // Token used when making an update request for a specific build. + // For example: Token for a Quick Fix Build: + // https://cloud.google.com/docs/chrome-enterprise/policies/?policy=DeviceQuickFixBuildToken + std::string autoupdate_token_; + DISALLOW_COPY_AND_ASSIGN(OmahaRequestParams); }; } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_ +#endif // UPDATE_ENGINE_CROS_OMAHA_REQUEST_PARAMS_H_ diff --git a/omaha_request_params_unittest.cc b/cros/omaha_request_params_unittest.cc index 73324319..2d67ec09 100644 --- a/omaha_request_params_unittest.cc +++ b/cros/omaha_request_params_unittest.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/omaha_request_params.h" +#include "update_engine/cros/omaha_request_params.h" #include <stdio.h> @@ -25,11 +25,10 @@ #include <gtest/gtest.h> #include "update_engine/common/constants.h" -#include "update_engine/common/fake_prefs.h" #include "update_engine/common/platform_constants.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" -#include "update_engine/fake_system_state.h" +#include "update_engine/cros/fake_system_state.h" using chromeos_update_engine::test_utils::WriteFileString; using std::string; @@ -38,26 +37,23 @@ namespace chromeos_update_engine { class OmahaRequestParamsTest : public ::testing::Test { public: - OmahaRequestParamsTest() : params_(&fake_system_state_) {} + OmahaRequestParamsTest() : params_() {} protected: void SetUp() override { // Create a uniquely named test directory. ASSERT_TRUE(tempdir_.CreateUniqueTempDir()); params_.set_root(tempdir_.GetPath().value()); + FakeSystemState::CreateInstance(); SetLockDown(false); - fake_system_state_.set_prefs(&fake_prefs_); } void SetLockDown(bool locked_down) { - fake_system_state_.fake_hardware()->SetIsOfficialBuild(locked_down); - fake_system_state_.fake_hardware()->SetIsNormalBootMode(locked_down); + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(locked_down); + FakeSystemState::Get()->fake_hardware()->SetIsNormalBootMode(locked_down); } - FakeSystemState fake_system_state_; - OmahaRequestParams params_{&fake_system_state_}; - FakePrefs fake_prefs_; - + OmahaRequestParams params_; base::ScopedTempDir tempdir_; }; @@ -75,73 +71,73 @@ string GetMachineType() { } // namespace TEST_F(OmahaRequestParamsTest, MissingChannelTest) { - EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_TRUE(params_.Init("", "", {})); // By default, if no channel is set, we should track the stable-channel. EXPECT_EQ("stable-channel", params_.target_channel()); } TEST_F(OmahaRequestParamsTest, ForceVersionTest) { - EXPECT_TRUE(params_.Init("ForcedVersion", "", false)); + EXPECT_TRUE(params_.Init("ForcedVersion", "", {})); EXPECT_EQ(string("ForcedVersion_") + GetMachineType(), params_.os_sp()); EXPECT_EQ("ForcedVersion", params_.app_version()); } TEST_F(OmahaRequestParamsTest, ForcedURLTest) { - EXPECT_TRUE(params_.Init("", "http://forced.google.com", false)); + EXPECT_TRUE(params_.Init("", "http://forced.google.com", {})); EXPECT_EQ("http://forced.google.com", params_.update_url()); } TEST_F(OmahaRequestParamsTest, MissingURLTest) { - EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_TRUE(params_.Init("", "", {})); EXPECT_EQ(constants::kOmahaDefaultProductionURL, params_.update_url()); } TEST_F(OmahaRequestParamsTest, DeltaOKTest) { - EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_TRUE(params_.Init("", "", {})); EXPECT_TRUE(params_.delta_okay()); } TEST_F(OmahaRequestParamsTest, NoDeltasTest) { ASSERT_TRUE( WriteFileString(tempdir_.GetPath().Append(".nodelta").value(), "")); - EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_TRUE(params_.Init("", "", {})); EXPECT_FALSE(params_.delta_okay()); } TEST_F(OmahaRequestParamsTest, SetTargetChannelTest) { { - OmahaRequestParams params(&fake_system_state_); + OmahaRequestParams params; params.set_root(tempdir_.GetPath().value()); - EXPECT_TRUE(params.Init("", "", false)); + EXPECT_TRUE(params.Init("", "", {})); EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr)); EXPECT_FALSE(params.mutable_image_props_.is_powerwash_allowed); } params_.set_root(tempdir_.GetPath().value()); - EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_TRUE(params_.Init("", "", {})); EXPECT_EQ("canary-channel", params_.target_channel()); EXPECT_FALSE(params_.mutable_image_props_.is_powerwash_allowed); } TEST_F(OmahaRequestParamsTest, SetIsPowerwashAllowedTest) { { - OmahaRequestParams params(&fake_system_state_); + OmahaRequestParams params; params.set_root(tempdir_.GetPath().value()); - EXPECT_TRUE(params.Init("", "", false)); + EXPECT_TRUE(params.Init("", "", {})); EXPECT_TRUE(params.SetTargetChannel("canary-channel", true, nullptr)); EXPECT_TRUE(params.mutable_image_props_.is_powerwash_allowed); } params_.set_root(tempdir_.GetPath().value()); - EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_TRUE(params_.Init("", "", {})); EXPECT_EQ("canary-channel", params_.target_channel()); EXPECT_TRUE(params_.mutable_image_props_.is_powerwash_allowed); } TEST_F(OmahaRequestParamsTest, SetTargetChannelInvalidTest) { { - OmahaRequestParams params(&fake_system_state_); + OmahaRequestParams params; params.set_root(tempdir_.GetPath().value()); SetLockDown(true); - EXPECT_TRUE(params.Init("", "", false)); + EXPECT_TRUE(params.Init("", "", {})); params.image_props_.allow_arbitrary_channels = false; string error_message; EXPECT_FALSE( @@ -151,7 +147,7 @@ TEST_F(OmahaRequestParamsTest, SetTargetChannelInvalidTest) { EXPECT_FALSE(params.mutable_image_props_.is_powerwash_allowed); } params_.set_root(tempdir_.GetPath().value()); - EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_TRUE(params_.Init("", "", {})); EXPECT_EQ("stable-channel", params_.target_channel()); EXPECT_FALSE(params_.mutable_image_props_.is_powerwash_allowed); } @@ -197,7 +193,7 @@ TEST_F(OmahaRequestParamsTest, SetTargetChannelWorks) { // When set to a valid value while a change is already pending, it should // succeed. - params_.Init("", "", false); + params_.Init("", "", {}); EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true, nullptr)); // The target channel should reflect the change, but the download channel // should continue to retain the old value ... @@ -236,6 +232,13 @@ TEST_F(OmahaRequestParamsTest, ToMoreStableChannelFlagTest) { EXPECT_FALSE(params_.ToMoreStableChannel()); } +TEST_F(OmahaRequestParamsTest, TargetChannelHintTest) { + EXPECT_TRUE(params_.Init("", "", {})); + const string kHint("foo-hint"); + params_.set_lts_tag(kHint); + EXPECT_EQ(kHint, params_.lts_tag()); +} + TEST_F(OmahaRequestParamsTest, ShouldPowerwashTest) { params_.mutable_image_props_.is_powerwash_allowed = false; EXPECT_FALSE(params_.ShouldPowerwash()); @@ -250,12 +253,42 @@ TEST_F(OmahaRequestParamsTest, ShouldPowerwashTest) { EXPECT_TRUE(params_.ShouldPowerwash()); } -TEST_F(OmahaRequestParamsTest, CollectECFWVersionsTest) { - params_.hwid_ = string("STUMPY ALEX 12345"); - EXPECT_FALSE(params_.CollectECFWVersions()); +TEST_F(OmahaRequestParamsTest, RequisitionIsSetTest) { + EXPECT_TRUE(params_.Init("", "", {})); + EXPECT_EQ("fake_requisition", params_.device_requisition()); +} + +TEST_F(OmahaRequestParamsTest, GetMissingDlcId) { + EXPECT_TRUE(params_.Init("", "", {})); + + string dlc_id; + EXPECT_FALSE(params_.GetDlcId("some-dlc-app-id", &dlc_id)); +} + +TEST_F(OmahaRequestParamsTest, GetDlcId) { + EXPECT_TRUE(params_.Init("", "", {})); + const string kExpectedDlcId = "test-dlc"; + const string dlc_app_id = params_.GetDlcAppId(kExpectedDlcId); + params_.set_dlc_apps_params({{dlc_app_id, {.name = kExpectedDlcId}}}); + + string dlc_id; + EXPECT_TRUE(params_.GetDlcId(dlc_app_id, &dlc_id)); + EXPECT_EQ(kExpectedDlcId, dlc_id); +} + +TEST_F(OmahaRequestParamsTest, GetDlcAppId) { + EXPECT_TRUE(params_.Init("", "", {})); + const string kAppId = "test-app-id"; + params_.set_app_id(kAppId); + const string kDlcId = "test-dlc"; + const string expected_dlc_app_id = kAppId + "_" + kDlcId; + + EXPECT_EQ(expected_dlc_app_id, params_.GetDlcAppId(kDlcId)); +} - params_.hwid_ = string("SNOW 12345"); - EXPECT_TRUE(params_.CollectECFWVersions()); +TEST_F(OmahaRequestParamsTest, AutoUpdateTokenTest) { + EXPECT_TRUE(params_.Init("", "", {.quick_fix_build_token = "foo-token"})); + EXPECT_EQ("foo-token", params_.autoupdate_token()); } } // namespace chromeos_update_engine diff --git a/omaha_response.h b/cros/omaha_response.h index 0ac09df1..3b07745a 100644 --- a/omaha_response.h +++ b/cros/omaha_response.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_OMAHA_RESPONSE_H_ -#define UPDATE_ENGINE_OMAHA_RESPONSE_H_ +#ifndef UPDATE_ENGINE_CROS_OMAHA_RESPONSE_H_ +#define UPDATE_ENGINE_CROS_OMAHA_RESPONSE_H_ #include <fcntl.h> #include <sys/stat.h> @@ -38,7 +38,6 @@ struct OmahaResponse { // These are only valid if update_exists is true: std::string version; - std::string system_version; struct Package { // The ordered list of URLs in the Omaha response. Each item is a complete @@ -51,6 +50,13 @@ struct OmahaResponse { // True if the payload described in this response is a delta payload. // False if it's a full payload. bool is_delta = false; + // True if the payload can be excluded from updating if consistently faulty. + // False if the payload is critical to update. + bool can_exclude = false; + // The App ID associated with the package. + std::string app_id; + // The unique fingerprint value associated with the package. + std::string fp; }; std::vector<Package> packages; @@ -102,9 +108,16 @@ struct OmahaResponse { // Key versions of the returned rollback image. Values are 0xffff if the // image not a rollback, or the fields were not present. RollbackKeyVersion rollback_key_version; + + // Key versions of the N - rollback_allowed_milestones release. For example, + // if the current version is 70 and rollback_allowed_milestones is 4, this + // will contain the key versions of version 66. This is used to ensure that + // the kernel and firmware keys are at most those of v66 so that v66 can be + // rolled back to. + RollbackKeyVersion past_rollback_key_version; }; static_assert(sizeof(off_t) == 8, "off_t not 64 bit"); } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_OMAHA_RESPONSE_H_ +#endif // UPDATE_ENGINE_CROS_OMAHA_RESPONSE_H_ diff --git a/omaha_response_handler_action.cc b/cros/omaha_response_handler_action.cc index ab41b848..04cae3e1 100644 --- a/omaha_response_handler_action.cc +++ b/cros/omaha_response_handler_action.cc @@ -14,26 +14,29 @@ // limitations under the License. // -#include "update_engine/omaha_response_handler_action.h" +#include "update_engine/cros/omaha_response_handler_action.h" #include <limits> #include <string> #include <base/logging.h> #include <base/strings/string_number_conversions.h> +#include <base/version.h> #include <policy/device_policy.h> #include "update_engine/common/constants.h" #include "update_engine/common/hardware_interface.h" #include "update_engine/common/prefs_interface.h" +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" -#include "update_engine/connection_manager_interface.h" -#include "update_engine/omaha_request_params.h" +#include "update_engine/cros/connection_manager_interface.h" +#include "update_engine/cros/omaha_request_params.h" +#include "update_engine/cros/payload_state_interface.h" #include "update_engine/payload_consumer/delta_performer.h" -#include "update_engine/payload_state_interface.h" #include "update_engine/update_manager/policy.h" #include "update_engine/update_manager/update_manager.h" +using chromeos_update_manager::kRollforwardInfinity; using chromeos_update_manager::Policy; using chromeos_update_manager::UpdateManager; using std::numeric_limits; @@ -41,10 +44,8 @@ using std::string; namespace chromeos_update_engine { -OmahaResponseHandlerAction::OmahaResponseHandlerAction( - SystemState* system_state) - : system_state_(system_state), - deadline_file_(constants::kOmahaResponseDeadlineFile) {} +OmahaResponseHandlerAction::OmahaResponseHandlerAction() + : deadline_file_(constants::kOmahaResponseDeadlineFile) {} void OmahaResponseHandlerAction::PerformAction() { CHECK(HasInputObject()); @@ -58,7 +59,7 @@ void OmahaResponseHandlerAction::PerformAction() { // All decisions as to which URL should be used have already been done. So, // make the current URL as the download URL. - string current_url = system_state_->payload_state()->GetCurrentUrl(); + string current_url = SystemState::Get()->payload_state()->GetCurrentUrl(); if (current_url.empty()) { // This shouldn't happen as we should always supply the HTTPS backup URL. // Handling this anyway, just in case. @@ -68,12 +69,15 @@ void OmahaResponseHandlerAction::PerformAction() { } // This is the url to the first package, not all packages. + // (For updates): All |Action|s prior to this must pass in non-excluded URLs + // within the |OmahaResponse|, reference exlusion logic in + // |OmahaRequestAction| and keep the enforcement of exclusions for updates. install_plan_.download_url = current_url; install_plan_.version = response.version; - install_plan_.system_version = response.system_version; - OmahaRequestParams* const params = system_state_->request_params(); - PayloadStateInterface* const payload_state = system_state_->payload_state(); + OmahaRequestParams* const params = SystemState::Get()->request_params(); + PayloadStateInterface* const payload_state = + SystemState::Get()->payload_state(); // If we're using p2p to download and there is a local peer, use it. if (payload_state->GetUsingP2PForDownloading() && @@ -96,36 +100,42 @@ void OmahaResponseHandlerAction::PerformAction() { return; } install_plan_.payloads.push_back( - {.size = package.size, + {.payload_urls = package.payload_urls, + .size = package.size, .metadata_size = package.metadata_size, .metadata_signature = package.metadata_signature, .hash = raw_hash, .type = package.is_delta ? InstallPayloadType::kDelta - : InstallPayloadType::kFull}); + : InstallPayloadType::kFull, + .fp = package.fp, + .app_id = package.app_id}); update_check_response_hash += package.hash + ":"; } install_plan_.public_key_rsa = response.public_key_rsa; install_plan_.hash_checks_mandatory = AreHashChecksMandatory(response); install_plan_.is_resume = DeltaPerformer::CanResumeUpdate( - system_state_->prefs(), update_check_response_hash); + SystemState::Get()->prefs(), update_check_response_hash); if (install_plan_.is_resume) { payload_state->UpdateResumed(); } else { payload_state->UpdateRestarted(); LOG_IF(WARNING, - !DeltaPerformer::ResetUpdateProgress(system_state_->prefs(), false)) + !DeltaPerformer::ResetUpdateProgress(SystemState::Get()->prefs(), + false)) << "Unable to reset the update progress."; LOG_IF(WARNING, - !system_state_->prefs()->SetString(kPrefsUpdateCheckResponseHash, - update_check_response_hash)) + !SystemState::Get()->prefs()->SetString( + kPrefsUpdateCheckResponseHash, update_check_response_hash)) << "Unable to save the update check response hash."; } if (params->is_install()) { - install_plan_.target_slot = system_state_->boot_control()->GetCurrentSlot(); + install_plan_.target_slot = + SystemState::Get()->boot_control()->GetCurrentSlot(); install_plan_.source_slot = BootControlInterface::kInvalidSlot; } else { - install_plan_.source_slot = system_state_->boot_control()->GetCurrentSlot(); + install_plan_.source_slot = + SystemState::Get()->boot_control()->GetCurrentSlot(); install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0; } @@ -135,8 +145,8 @@ void OmahaResponseHandlerAction::PerformAction() { // downloaded from. string current_channel_key = kPrefsChannelOnSlotPrefix + std::to_string(install_plan_.target_slot); - system_state_->prefs()->SetString(current_channel_key, - params->download_channel()); + SystemState::Get()->prefs()->SetString(current_channel_key, + params->download_channel()); // Checking whether device is able to boot up the returned rollback image. if (response.is_rollback) { @@ -145,10 +155,13 @@ void OmahaResponseHandlerAction::PerformAction() { completer.set_code(ErrorCode::kOmahaResponseInvalid); return; } + + // Calculate the values on the version values on current device. auto min_kernel_key_version = static_cast<uint32_t>( - system_state_->hardware()->GetMinKernelKeyVersion()); + SystemState::Get()->hardware()->GetMinKernelKeyVersion()); auto min_firmware_key_version = static_cast<uint32_t>( - system_state_->hardware()->GetMinFirmwareKeyVersion()); + SystemState::Get()->hardware()->GetMinFirmwareKeyVersion()); + uint32_t kernel_key_version = static_cast<uint32_t>(response.rollback_key_version.kernel_key) << 16 | static_cast<uint32_t>(response.rollback_key_version.kernel); @@ -157,6 +170,12 @@ void OmahaResponseHandlerAction::PerformAction() { << 16 | static_cast<uint32_t>(response.rollback_key_version.firmware); + LOG(INFO) << "Rollback image versions:" + << " device_kernel_key_version=" << min_kernel_key_version + << " image_kernel_key_version=" << kernel_key_version + << " device_firmware_key_version=" << min_firmware_key_version + << " image_firmware_key_version=" << firmware_key_version; + // Don't attempt a rollback if the versions are incompatible or the // target image does not specify the version information. if (kernel_key_version == numeric_limits<uint32_t>::max() || @@ -168,15 +187,40 @@ void OmahaResponseHandlerAction::PerformAction() { return; } install_plan_.is_rollback = true; + install_plan_.rollback_data_save_requested = + params->rollback_data_save_requested(); } - if (response.powerwash_required || params->ShouldPowerwash()) + // Powerwash if either the response requires it or the parameters indicated + // powerwash (usually because there was a channel downgrade) and we are + // downgrading the version. Enterprise rollback, indicated by + // |response.is_rollback| is dealt with separately above. + if (response.powerwash_required) { install_plan_.powerwash_required = true; + } else if (params->ShouldPowerwash() && !response.is_rollback) { + base::Version new_version(response.version); + base::Version current_version(params->app_version()); + + if (!new_version.IsValid()) { + LOG(WARNING) << "Not powerwashing," + << " the update's version number is unreadable." + << " Update's version number: " << response.version; + } else if (!current_version.IsValid()) { + LOG(WARNING) << "Not powerwashing," + << " the current version number is unreadable." + << " Current version number: " << params->app_version(); + } else if (new_version < current_version) { + install_plan_.powerwash_required = true; + // Always try to preserve enrollment and wifi data for enrolled devices. + install_plan_.rollback_data_save_requested = + SystemState::Get()->device_policy() && + SystemState::Get()->device_policy()->IsEnterpriseEnrolled(); + } + } TEST_AND_RETURN(HasOutputPipe()); if (HasOutputPipe()) SetOutputObject(install_plan_); - LOG(INFO) << "Using this install plan:"; install_plan_.Dump(); // Send the deadline data (if any) to Chrome through a file. This is a pretty @@ -202,12 +246,60 @@ void OmahaResponseHandlerAction::PerformAction() { // Check the generated install-plan with the Policy to confirm that // it can be applied at this time (or at all). - UpdateManager* const update_manager = system_state_->update_manager(); + UpdateManager* const update_manager = SystemState::Get()->update_manager(); CHECK(update_manager); auto ec = ErrorCode::kSuccess; update_manager->PolicyRequest( &Policy::UpdateCanBeApplied, &ec, &install_plan_); completer.set_code(ec); + + const auto allowed_milestones = params->rollback_allowed_milestones(); + if (allowed_milestones > 0) { + auto max_firmware_rollforward = numeric_limits<uint32_t>::max(); + auto max_kernel_rollforward = numeric_limits<uint32_t>::max(); + + // Determine the version to update the max rollforward verified boot + // value. + OmahaResponse::RollbackKeyVersion version = + response.past_rollback_key_version; + + // Determine the max rollforward values to be set in the TPM. + max_firmware_rollforward = static_cast<uint32_t>(version.firmware_key) + << 16 | + static_cast<uint32_t>(version.firmware); + max_kernel_rollforward = static_cast<uint32_t>(version.kernel_key) << 16 | + static_cast<uint32_t>(version.kernel); + + // In the case that the value is 0xffffffff, log a warning because the + // device should not be installing a rollback image without having version + // information. + if (max_firmware_rollforward == numeric_limits<uint32_t>::max() || + max_kernel_rollforward == numeric_limits<uint32_t>::max()) { + LOG(WARNING) + << "Max rollforward values were not sent in rollback response: " + << " max_kernel_rollforward=" << max_kernel_rollforward + << " max_firmware_rollforward=" << max_firmware_rollforward + << " rollback_allowed_milestones=" + << params->rollback_allowed_milestones(); + } else { + LOG(INFO) << "Setting the max rollforward values: " + << " max_kernel_rollforward=" << max_kernel_rollforward + << " max_firmware_rollforward=" << max_firmware_rollforward + << " rollback_allowed_milestones=" + << params->rollback_allowed_milestones(); + SystemState::Get()->hardware()->SetMaxKernelKeyRollforward( + max_kernel_rollforward); + // TODO(crbug/783998): Set max firmware rollforward when implemented. + } + } else { + LOG(INFO) << "Rollback is not allowed. Setting max rollforward values" + << " to infinity"; + // When rollback is not allowed, explicitly set the max roll forward to + // infinity. + SystemState::Get()->hardware()->SetMaxKernelKeyRollforward( + kRollforwardInfinity); + // TODO(crbug/783998): Set max firmware rollforward when implemented. + } } bool OmahaResponseHandlerAction::AreHashChecksMandatory( @@ -225,8 +317,8 @@ bool OmahaResponseHandlerAction::AreHashChecksMandatory( // devmode/debugd checks pass, in which case the hash is waived. // * Dev/test image: // - Any URL is allowed through with no hash checking. - if (!system_state_->request_params()->IsUpdateUrlOfficial() || - !system_state_->hardware()->IsOfficialBuild()) { + if (!SystemState::Get()->request_params()->IsUpdateUrlOfficial() || + !SystemState::Get()->hardware()->IsOfficialBuild()) { // Still do a hash check if a public key is included. if (!response.public_key_rsa.empty()) { // The autoupdate_CatchBadSignatures test checks for this string diff --git a/omaha_response_handler_action.h b/cros/omaha_response_handler_action.h index d2e6db86..9842c942 100644 --- a/omaha_response_handler_action.h +++ b/cros/omaha_response_handler_action.h @@ -14,17 +14,16 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H_ -#define UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H_ +#ifndef UPDATE_ENGINE_CROS_OMAHA_RESPONSE_HANDLER_ACTION_H_ +#define UPDATE_ENGINE_CROS_OMAHA_RESPONSE_HANDLER_ACTION_H_ #include <string> #include <gtest/gtest_prod.h> // for FRIEND_TEST #include "update_engine/common/action.h" -#include "update_engine/omaha_request_action.h" +#include "update_engine/cros/omaha_request_action.h" #include "update_engine/payload_consumer/install_plan.h" -#include "update_engine/system_state.h" // This class reads in an Omaha response and converts what it sees into // an install plan which is passed out. @@ -42,7 +41,7 @@ class ActionTraits<OmahaResponseHandlerAction> { class OmahaResponseHandlerAction : public Action<OmahaResponseHandlerAction> { public: - explicit OmahaResponseHandlerAction(SystemState* system_state); + OmahaResponseHandlerAction(); typedef ActionTraits<OmahaResponseHandlerAction>::InputObjectType InputObjectType; @@ -65,9 +64,6 @@ class OmahaResponseHandlerAction : public Action<OmahaResponseHandlerAction> { // of the system and the contents of the Omaha response. False otherwise. bool AreHashChecksMandatory(const OmahaResponse& response); - // Global system context. - SystemState* system_state_; - // The install plan, if we have an update. InstallPlan install_plan_; @@ -90,4 +86,4 @@ class OmahaResponseHandlerAction : public Action<OmahaResponseHandlerAction> { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H_ +#endif // UPDATE_ENGINE_CROS_OMAHA_RESPONSE_HANDLER_ACTION_H_ diff --git a/omaha_response_handler_action_unittest.cc b/cros/omaha_response_handler_action_unittest.cc index b47040b6..c9b46b15 100644 --- a/omaha_response_handler_action_unittest.cc +++ b/cros/omaha_response_handler_action_unittest.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/omaha_response_handler_action.h" +#include "update_engine/cros/omaha_response_handler_action.h" #include <memory> #include <string> @@ -29,8 +29,8 @@ #include "update_engine/common/platform_constants.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" -#include "update_engine/fake_system_state.h" -#include "update_engine/mock_payload_state.h" +#include "update_engine/cros/fake_system_state.h" +#include "update_engine/cros/mock_payload_state.h" #include "update_engine/payload_consumer/payload_constants.h" #include "update_engine/update_manager/mock_policy.h" @@ -38,6 +38,7 @@ using chromeos_update_engine::test_utils::System; using chromeos_update_engine::test_utils::WriteFileString; using chromeos_update_manager::EvalStatus; using chromeos_update_manager::FakeUpdateManager; +using chromeos_update_manager::kRollforwardInfinity; using chromeos_update_manager::MockPolicy; using std::string; using testing::_; @@ -79,7 +80,11 @@ class OmahaResponseHandlerActionProcessorDelegate class OmahaResponseHandlerActionTest : public ::testing::Test { protected: void SetUp() override { - FakeBootControl* fake_boot_control = fake_system_state_.fake_boot_control(); + FakeSystemState::CreateInstance(); + // Enable MockPrefs; + FakeSystemState::Get()->set_prefs(nullptr); + FakeBootControl* fake_boot_control = + FakeSystemState::Get()->fake_boot_control(); fake_boot_control->SetPartitionDevice(kPartitionNameKernel, 0, "/dev/sdz2"); fake_boot_control->SetPartitionDevice(kPartitionNameRoot, 0, "/dev/sdz3"); fake_boot_control->SetPartitionDevice(kPartitionNameKernel, 1, "/dev/sdz4"); @@ -99,7 +104,6 @@ class OmahaResponseHandlerActionTest : public ::testing::Test { // it in non-success cases. ErrorCode action_result_code_; - FakeSystemState fake_system_state_; // "Hash+" const brillo::Blob expected_hash_ = {0x48, 0x61, 0x73, 0x68, 0x2b}; }; @@ -116,6 +120,9 @@ const char* const kLongName = "-the_update_a.b.c.d_DELTA_.tgz"; const char* const kBadVersion = "don't update me"; const char* const kPayloadHashHex = "486173682b"; +const char* const kPayloadFp1 = "1.755aff78ec73dfc7f590893ac"; +const char* const kPayloadFp2 = "1.98ba213e0ccec0d0e8cdc74a5"; +const char* const kPayloadAppId = "test_app_id"; } // namespace bool OmahaResponseHandlerActionTest::DoTest(const OmahaResponse& in, @@ -132,25 +139,25 @@ bool OmahaResponseHandlerActionTest::DoTest(const OmahaResponse& in, string expected_hash; for (const auto& package : in.packages) expected_hash += package.hash + ":"; - EXPECT_CALL(*(fake_system_state_.mock_prefs()), + EXPECT_CALL(*(FakeSystemState::Get()->mock_prefs()), SetString(kPrefsUpdateCheckResponseHash, expected_hash)) .WillOnce(Return(true)); int slot = - fake_system_state_.request_params()->is_install() - ? fake_system_state_.fake_boot_control()->GetCurrentSlot() - : 1 - fake_system_state_.fake_boot_control()->GetCurrentSlot(); + FakeSystemState::Get()->request_params()->is_install() + ? FakeSystemState::Get()->fake_boot_control()->GetCurrentSlot() + : 1 - FakeSystemState::Get()->fake_boot_control()->GetCurrentSlot(); string key = kPrefsChannelOnSlotPrefix + std::to_string(slot); - EXPECT_CALL(*(fake_system_state_.mock_prefs()), SetString(key, testing::_)) + EXPECT_CALL(*(FakeSystemState::Get()->mock_prefs()), + SetString(key, testing::_)) .WillOnce(Return(true)); } string current_url = in.packages.size() ? in.packages[0].payload_urls[0] : ""; - EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetCurrentUrl()) + EXPECT_CALL(*(FakeSystemState::Get()->mock_payload_state()), GetCurrentUrl()) .WillRepeatedly(Return(current_url)); - auto response_handler_action = - std::make_unique<OmahaResponseHandlerAction>(&fake_system_state_); + auto response_handler_action = std::make_unique<OmahaResponseHandlerAction>(); if (!test_deadline_file.empty()) response_handler_action->deadline_file_ = test_deadline_file; @@ -175,7 +182,7 @@ bool OmahaResponseHandlerActionTest::DoTest(const OmahaResponse& in, } TEST_F(OmahaResponseHandlerActionTest, SimpleTest) { - test_utils::ScopedTempFile test_deadline_file( + ScopedTempFile test_deadline_file( "omaha_response_handler_action_unittest-XXXXXX"); { OmahaResponse in; @@ -184,7 +191,9 @@ TEST_F(OmahaResponseHandlerActionTest, SimpleTest) { in.packages.push_back( {.payload_urls = {"http://foo/the_update_a.b.c.d.tgz"}, .size = 12, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); in.more_info_url = "http://more/info"; in.prompt = false; in.deadline = "20101020"; @@ -192,6 +201,8 @@ TEST_F(OmahaResponseHandlerActionTest, SimpleTest) { EXPECT_TRUE(DoTest(in, test_deadline_file.path(), &install_plan)); EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); EXPECT_EQ(1U, install_plan.target_slot); string deadline; EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline)); @@ -210,15 +221,19 @@ TEST_F(OmahaResponseHandlerActionTest, SimpleTest) { in.packages.push_back( {.payload_urls = {"http://foo/the_update_a.b.c.d.tgz"}, .size = 12, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); in.more_info_url = "http://more/info"; in.prompt = true; InstallPlan install_plan; // Set the other slot as current. - fake_system_state_.fake_boot_control()->SetCurrentSlot(1); + FakeSystemState::Get()->fake_boot_control()->SetCurrentSlot(1); EXPECT_TRUE(DoTest(in, test_deadline_file.path(), &install_plan)); EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); EXPECT_EQ(0U, install_plan.target_slot); string deadline; EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline) && @@ -229,21 +244,26 @@ TEST_F(OmahaResponseHandlerActionTest, SimpleTest) { OmahaResponse in; in.update_exists = true; in.version = "a.b.c.d"; - in.packages.push_back( - {.payload_urls = {kLongName}, .size = 12, .hash = kPayloadHashHex}); + in.packages.push_back({.payload_urls = {kLongName}, + .size = 12, + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); in.more_info_url = "http://more/info"; in.prompt = true; in.deadline = "some-deadline"; InstallPlan install_plan; - fake_system_state_.fake_boot_control()->SetCurrentSlot(0); + FakeSystemState::Get()->fake_boot_control()->SetCurrentSlot(0); // Because rollback happened, the deadline shouldn't be written into the // file. - EXPECT_CALL(*(fake_system_state_.mock_payload_state()), + EXPECT_CALL(*(FakeSystemState::Get()->mock_payload_state()), GetRollbackHappened()) .WillOnce(Return(true)); EXPECT_TRUE(DoTest(in, test_deadline_file.path(), &install_plan)); EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); EXPECT_EQ(1U, install_plan.target_slot); string deadline; EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline)); @@ -254,19 +274,24 @@ TEST_F(OmahaResponseHandlerActionTest, SimpleTest) { OmahaResponse in; in.update_exists = true; in.version = "a.b.c.d"; - in.packages.push_back( - {.payload_urls = {kLongName}, .size = 12, .hash = kPayloadHashHex}); + in.packages.push_back({.payload_urls = {kLongName}, + .size = 12, + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); in.more_info_url = "http://more/info"; in.prompt = true; in.deadline = "some-deadline"; InstallPlan install_plan; - fake_system_state_.fake_boot_control()->SetCurrentSlot(0); - EXPECT_CALL(*(fake_system_state_.mock_payload_state()), + FakeSystemState::Get()->fake_boot_control()->SetCurrentSlot(0); + EXPECT_CALL(*(FakeSystemState::Get()->mock_payload_state()), GetRollbackHappened()) .WillOnce(Return(false)); EXPECT_TRUE(DoTest(in, test_deadline_file.path(), &install_plan)); EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); EXPECT_EQ(1U, install_plan.target_slot); string deadline; EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline)); @@ -293,10 +318,10 @@ TEST_F(OmahaResponseHandlerActionTest, InstallTest) { {.payload_urls = {kLongName}, .size = 2, .hash = kPayloadHashHex}); in.more_info_url = "http://more/info"; - OmahaRequestParams params(&fake_system_state_); + OmahaRequestParams params; params.set_is_install(true); - fake_system_state_.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); EXPECT_EQ(install_plan.source_slot, UINT_MAX); @@ -308,10 +333,14 @@ TEST_F(OmahaResponseHandlerActionTest, MultiPackageTest) { in.version = "a.b.c.d"; in.packages.push_back({.payload_urls = {"http://package/1"}, .size = 1, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); in.packages.push_back({.payload_urls = {"http://package/2"}, .size = 2, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp2}); in.more_info_url = "http://more/info"; InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); @@ -321,6 +350,10 @@ TEST_F(OmahaResponseHandlerActionTest, MultiPackageTest) { EXPECT_EQ(in.packages[1].size, install_plan.payloads[1].size); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); EXPECT_EQ(expected_hash_, install_plan.payloads[1].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[1].app_id, install_plan.payloads[1].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); + EXPECT_EQ(in.packages[1].fp, install_plan.payloads[1].fp); EXPECT_EQ(in.version, install_plan.version); } @@ -331,16 +364,20 @@ TEST_F(OmahaResponseHandlerActionTest, HashChecksForHttpTest) { in.packages.push_back( {.payload_urls = {"http://test.should/need/hash.checks.signed"}, .size = 12, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); in.more_info_url = "http://more/info"; // Hash checks are always skipped for non-official update URLs. - EXPECT_CALL(*(fake_system_state_.mock_request_params()), + EXPECT_CALL(*(FakeSystemState::Get()->mock_request_params()), IsUpdateUrlOfficial()) .WillRepeatedly(Return(true)); InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); EXPECT_TRUE(install_plan.hash_checks_mandatory); EXPECT_EQ(in.version, install_plan.version); } @@ -352,15 +389,19 @@ TEST_F(OmahaResponseHandlerActionTest, HashChecksForUnofficialUpdateUrl) { in.packages.push_back( {.payload_urls = {"http://url.normally/needs/hash.checks.signed"}, .size = 12, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); in.more_info_url = "http://more/info"; - EXPECT_CALL(*(fake_system_state_.mock_request_params()), + EXPECT_CALL(*(FakeSystemState::Get()->mock_request_params()), IsUpdateUrlOfficial()) .WillRepeatedly(Return(false)); InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); EXPECT_FALSE(install_plan.hash_checks_mandatory); EXPECT_EQ(in.version, install_plan.version); } @@ -374,16 +415,20 @@ TEST_F(OmahaResponseHandlerActionTest, in.packages.push_back( {.payload_urls = {"http://url.normally/needs/hash.checks.signed"}, .size = 12, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); in.more_info_url = "http://more/info"; - EXPECT_CALL(*(fake_system_state_.mock_request_params()), + EXPECT_CALL(*(FakeSystemState::Get()->mock_request_params()), IsUpdateUrlOfficial()) .WillRepeatedly(Return(true)); - fake_system_state_.fake_hardware()->SetIsOfficialBuild(false); + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(false); InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); EXPECT_FALSE(install_plan.hash_checks_mandatory); EXPECT_EQ(in.version, install_plan.version); } @@ -395,15 +440,19 @@ TEST_F(OmahaResponseHandlerActionTest, HashChecksForHttpsTest) { in.packages.push_back( {.payload_urls = {"https://test.should/need/hash.checks.signed"}, .size = 12, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); in.more_info_url = "http://more/info"; - EXPECT_CALL(*(fake_system_state_.mock_request_params()), + EXPECT_CALL(*(FakeSystemState::Get()->mock_request_params()), IsUpdateUrlOfficial()) .WillRepeatedly(Return(true)); InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); EXPECT_TRUE(install_plan.hash_checks_mandatory); EXPECT_EQ(in.version, install_plan.version); } @@ -416,23 +465,28 @@ TEST_F(OmahaResponseHandlerActionTest, HashChecksForBothHttpAndHttpsTest) { {.payload_urls = {"http://test.should.still/need/hash.checks", "https://test.should.still/need/hash.checks"}, .size = 12, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); in.more_info_url = "http://more/info"; - EXPECT_CALL(*(fake_system_state_.mock_request_params()), + EXPECT_CALL(*(FakeSystemState::Get()->mock_request_params()), IsUpdateUrlOfficial()) .WillRepeatedly(Return(true)); InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); EXPECT_TRUE(install_plan.hash_checks_mandatory); EXPECT_EQ(in.version, install_plan.version); } -TEST_F(OmahaResponseHandlerActionTest, ChangeToMoreStableChannelTest) { +TEST_F(OmahaResponseHandlerActionTest, + ChangeToMoreStableVersionAndChannelTest) { OmahaResponse in; in.update_exists = true; - in.version = "a.b.c.d"; + in.version = "1.0.0.0"; in.packages.push_back({.payload_urls = {"https://MoreStableChannelTest"}, .size = 1, .hash = kPayloadHashHex}); @@ -442,29 +496,207 @@ TEST_F(OmahaResponseHandlerActionTest, ChangeToMoreStableChannelTest) { base::ScopedTempDir tempdir; ASSERT_TRUE(tempdir.CreateUniqueTempDir()); - OmahaRequestParams params(&fake_system_state_); - fake_system_state_.fake_hardware()->SetIsOfficialBuild(false); + OmahaRequestParams params; + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(false); params.set_root(tempdir.GetPath().value()); params.set_current_channel("canary-channel"); - // The ImageProperties in Android uses prefs to store MutableImageProperties. -#ifdef __ANDROID__ - EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, true)) - .WillOnce(Return(true)); -#endif // __ANDROID__ EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr)); params.UpdateDownloadChannel(); - EXPECT_TRUE(params.ShouldPowerwash()); + params.set_app_version("2.0.0.0"); - fake_system_state_.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); EXPECT_TRUE(install_plan.powerwash_required); } -TEST_F(OmahaResponseHandlerActionTest, ChangeToLessStableChannelTest) { +TEST_F(OmahaResponseHandlerActionTest, + ChangeToMoreStableVersionAndChannelPowerwashNotAllowedTest) { OmahaResponse in; in.update_exists = true; - in.version = "a.b.c.d"; + in.version = "1.0.0.0"; + in.packages.push_back({.payload_urls = {"https://MoreStableChannelTest"}, + .size = 1, + .hash = kPayloadHashHex}); + in.more_info_url = "http://more/info"; + + // Create a uniquely named test directory. + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + + OmahaRequestParams params; + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(false); + params.set_root(tempdir.GetPath().value()); + params.set_current_channel("canary-channel"); + EXPECT_TRUE(params.SetTargetChannel("stable-channel", false, nullptr)); + params.UpdateDownloadChannel(); + params.set_app_version("2.0.0.0"); + + FakeSystemState::Get()->set_request_params(¶ms); + InstallPlan install_plan; + EXPECT_TRUE(DoTest(in, "", &install_plan)); + EXPECT_FALSE(install_plan.powerwash_required); +} + +TEST_F(OmahaResponseHandlerActionTest, + ChangeToMoreStableChannelButNewerVersionTest) { + OmahaResponse in; + in.update_exists = true; + in.version = "12345.96.0.0"; + in.packages.push_back({.payload_urls = {"https://ChannelDownVersionUp"}, + .size = 1, + .hash = kPayloadHashHex}); + in.more_info_url = "http://more/info"; + + // Create a uniquely named test directory. + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + + OmahaRequestParams params; + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(false); + params.set_root(tempdir.GetPath().value()); + params.set_current_channel("beta-channel"); + EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr)); + params.UpdateDownloadChannel(); + params.set_app_version("12345.48.0.0"); + + FakeSystemState::Get()->set_request_params(¶ms); + InstallPlan install_plan; + EXPECT_TRUE(DoTest(in, "", &install_plan)); + EXPECT_FALSE(install_plan.powerwash_required); +} + +TEST_F(OmahaResponseHandlerActionTest, + ChangeToMoreStableChannelButSameVersionTest) { + OmahaResponse in; + in.update_exists = true; + in.version = "12345.0.0.0"; + in.packages.push_back({.payload_urls = {"https://ChannelDownVersionUp"}, + .size = 1, + .hash = kPayloadHashHex}); + in.more_info_url = "http://more/info"; + + // Create a uniquely named test directory. + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + + OmahaRequestParams params; + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(false); + params.set_root(tempdir.GetPath().value()); + params.set_current_channel("beta-channel"); + EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr)); + params.UpdateDownloadChannel(); + params.set_app_version("12345.0.0.0"); + + FakeSystemState::Get()->set_request_params(¶ms); + InstallPlan install_plan; + EXPECT_TRUE(DoTest(in, "", &install_plan)); + EXPECT_FALSE(install_plan.powerwash_required); + EXPECT_FALSE(install_plan.rollback_data_save_requested); +} + +// On an enrolled device, the rollback data restore should be attempted when +// doing a powerwash and channel downgrade. +TEST_F(OmahaResponseHandlerActionTest, + ChangeToMoreStableChannelEnrolledDataRestore) { + OmahaResponse in; + in.update_exists = true; + in.version = "12345.96.0.0"; + in.packages.push_back({.payload_urls = {"https://ChannelDownEnrolled"}, + .size = 1, + .hash = kPayloadHashHex}); + in.more_info_url = "http://more/info"; + + // Create a uniquely named test directory. + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + + OmahaRequestParams params; + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(true); + params.set_root(tempdir.GetPath().value()); + params.set_current_channel("beta-channel"); + EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr)); + params.UpdateDownloadChannel(); + params.set_app_version("12347.48.0.0"); + + testing::NiceMock<policy::MockDevicePolicy> mock_device_policy; + EXPECT_CALL(mock_device_policy, IsEnterpriseEnrolled()) + .WillOnce(Return(true)); + FakeSystemState::Get()->set_device_policy(&mock_device_policy); + + FakeSystemState::Get()->set_request_params(¶ms); + InstallPlan install_plan; + EXPECT_TRUE(DoTest(in, "", &install_plan)); + EXPECT_TRUE(install_plan.rollback_data_save_requested); +} + +// Never attempt rollback data restore if the device is not enrolled. +TEST_F(OmahaResponseHandlerActionTest, + ChangeToMoreStableChannelUnenrolledNoDataRestore) { + OmahaResponse in; + in.update_exists = true; + in.version = "12345.96.0.0"; + in.packages.push_back({.payload_urls = {"https://ChannelDownEnrolled"}, + .size = 1, + .hash = kPayloadHashHex}); + in.more_info_url = "http://more/info"; + + // Create a uniquely named test directory. + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + + OmahaRequestParams params; + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(true); + params.set_root(tempdir.GetPath().value()); + params.set_current_channel("beta-channel"); + EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr)); + params.UpdateDownloadChannel(); + params.set_app_version("12347.48.0.0"); + + testing::NiceMock<policy::MockDevicePolicy> mock_device_policy; + EXPECT_CALL(mock_device_policy, IsEnterpriseEnrolled()) + .WillOnce(Return(false)); + FakeSystemState::Get()->set_device_policy(&mock_device_policy); + + FakeSystemState::Get()->set_request_params(¶ms); + InstallPlan install_plan; + EXPECT_TRUE(DoTest(in, "", &install_plan)); + EXPECT_FALSE(install_plan.rollback_data_save_requested); +} + +// Never attempt rollback data restore if powerwash is not allowed. +TEST_F(OmahaResponseHandlerActionTest, + ChangeToMoreStableChannelNoPowerwashNoDataRestore) { + OmahaResponse in; + in.update_exists = true; + in.version = "12345.96.0.0"; + in.packages.push_back( + {.payload_urls = {"https://URL"}, .size = 1, .hash = kPayloadHashHex}); + in.more_info_url = "http://more/info"; + + // Create a uniquely named test directory. + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + + OmahaRequestParams params; + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(true); + params.set_root(tempdir.GetPath().value()); + params.set_current_channel("beta-channel"); + EXPECT_TRUE(params.SetTargetChannel("stable-channel", false, nullptr)); + params.UpdateDownloadChannel(); + params.set_app_version("12347.48.0.0"); + + FakeSystemState::Get()->set_request_params(¶ms); + InstallPlan install_plan; + EXPECT_TRUE(DoTest(in, "", &install_plan)); + EXPECT_FALSE(install_plan.rollback_data_save_requested); +} + +TEST_F(OmahaResponseHandlerActionTest, + ChangeToLessStableVersionAndChannelTest) { + OmahaResponse in; + in.update_exists = true; + in.version = "2.0.0.0"; in.packages.push_back({.payload_urls = {"https://LessStableChannelTest"}, .size = 15, .hash = kPayloadHashHex}); @@ -474,20 +706,15 @@ TEST_F(OmahaResponseHandlerActionTest, ChangeToLessStableChannelTest) { base::ScopedTempDir tempdir; ASSERT_TRUE(tempdir.CreateUniqueTempDir()); - OmahaRequestParams params(&fake_system_state_); - fake_system_state_.fake_hardware()->SetIsOfficialBuild(false); + OmahaRequestParams params; + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(false); params.set_root(tempdir.GetPath().value()); params.set_current_channel("stable-channel"); - // The ImageProperties in Android uses prefs to store MutableImageProperties. -#ifdef __ANDROID__ - EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, false)) - .WillOnce(Return(true)); -#endif // __ANDROID__ EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr)); params.UpdateDownloadChannel(); - EXPECT_FALSE(params.ShouldPowerwash()); + params.set_app_version("1.0.0.0"); - fake_system_state_.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); EXPECT_FALSE(install_plan.powerwash_required); @@ -500,29 +727,33 @@ TEST_F(OmahaResponseHandlerActionTest, P2PUrlIsUsedAndHashChecksMandatory) { in.packages.push_back( {.payload_urls = {"https://would.not/cause/hash/checks"}, .size = 12, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); in.more_info_url = "http://more/info"; - OmahaRequestParams params(&fake_system_state_); + OmahaRequestParams params; // We're using a real OmahaRequestParams object here so we can't mock // IsUpdateUrlOfficial(), but setting the update URL to the AutoUpdate test // server will cause IsUpdateUrlOfficial() to return true. params.set_update_url(constants::kOmahaDefaultAUTestURL); - fake_system_state_.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); - EXPECT_CALL(*fake_system_state_.mock_payload_state(), + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), SetUsingP2PForDownloading(true)); string p2p_url = "http://9.8.7.6/p2p"; - EXPECT_CALL(*fake_system_state_.mock_payload_state(), GetP2PUrl()) + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), GetP2PUrl()) .WillRepeatedly(Return(p2p_url)); - EXPECT_CALL(*fake_system_state_.mock_payload_state(), + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), GetUsingP2PForDownloading()) .WillRepeatedly(Return(true)); InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); EXPECT_EQ(p2p_url, install_plan.download_url); EXPECT_TRUE(install_plan.hash_checks_mandatory); } @@ -534,21 +765,46 @@ TEST_F(OmahaResponseHandlerActionTest, RollbackTest) { .size = 1, .hash = kPayloadHashHex}); in.is_rollback = true; - in.rollback_key_version.kernel = 1; - in.rollback_key_version.kernel = 2; - in.rollback_key_version.firmware_key = 3; - in.rollback_key_version.firmware = 4; - fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002); - fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004); + // The rollback payload is 2 versions behind stable. + in.rollback_key_version.kernel = 24; + in.rollback_key_version.kernel = 23; + in.rollback_key_version.firmware_key = 22; + in.rollback_key_version.firmware = 21; + + OmahaResponse::RollbackKeyVersion m4; + m4.firmware_key = 16; + m4.firmware = 15; + m4.kernel_key = 14; + m4.kernel = 13; + + in.past_rollback_key_version = m4; + + FakeSystemState::Get()->fake_hardware()->SetMinKernelKeyVersion(0x00010002); + FakeSystemState::Get()->fake_hardware()->SetMinFirmwareKeyVersion(0x00030004); + + FakeSystemState::Get()->fake_hardware()->SetMaxKernelKeyRollforward( + 0xaaaaaaaa); + // TODO(crbug/783998): Add support for firmware when implemented. - OmahaRequestParams params(&fake_system_state_); + OmahaRequestParams params; params.set_rollback_allowed(true); + params.set_rollback_allowed_milestones(4); - fake_system_state_.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); EXPECT_TRUE(install_plan.is_rollback); + + // The max rollforward should be set the values of the image + // rollback_allowed_milestones (4 for this test) in the past. + const uint32_t expected_max_kernel_rollforward = + static_cast<uint32_t>(m4.kernel_key) << 16 | + static_cast<uint32_t>(m4.kernel); + EXPECT_EQ( + expected_max_kernel_rollforward, + FakeSystemState::Get()->fake_hardware()->GetMaxKernelKeyRollforward()); + // TODO(crbug/783998): Add support for firmware when implemented. } TEST_F(OmahaResponseHandlerActionTest, RollbackKernelVersionErrorTest) { @@ -563,18 +819,37 @@ TEST_F(OmahaResponseHandlerActionTest, RollbackKernelVersionErrorTest) { in.rollback_key_version.firmware_key = 3; in.rollback_key_version.firmware = 4; - fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002); - fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004); + OmahaResponse::RollbackKeyVersion m4; + m4.firmware_key = 16; + m4.firmware = 15; + m4.kernel_key = 14; + m4.kernel = 13; + in.past_rollback_key_version = m4; + + FakeSystemState::Get()->fake_hardware()->SetMinKernelKeyVersion(0x00010002); + FakeSystemState::Get()->fake_hardware()->SetMinFirmwareKeyVersion(0x00030004); + const uint32_t current_kernel_max_rollforward = 0xaaaaaaaa; + FakeSystemState::Get()->fake_hardware()->SetMaxKernelKeyRollforward( + current_kernel_max_rollforward); - OmahaRequestParams params(&fake_system_state_); + OmahaRequestParams params; params.set_rollback_allowed(true); + params.set_rollback_allowed_milestones(4); - fake_system_state_.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); InstallPlan install_plan; EXPECT_FALSE(DoTest(in, "", &install_plan)); + + // Max rollforward is not changed in error cases. + EXPECT_EQ( + current_kernel_max_rollforward, + FakeSystemState::Get()->fake_hardware()->GetMaxKernelKeyRollforward()); + // TODO(crbug/783998): Add support for firmware when implemented. } TEST_F(OmahaResponseHandlerActionTest, RollbackFirmwareVersionErrorTest) { + // TODO(crbug/783998): Add handling for max_firmware_rollforward when + // implemented. OmahaResponse in; in.update_exists = true; in.packages.push_back({.payload_urls = {"https://RollbackTest"}, @@ -586,13 +861,14 @@ TEST_F(OmahaResponseHandlerActionTest, RollbackFirmwareVersionErrorTest) { in.rollback_key_version.firmware_key = 3; in.rollback_key_version.firmware = 3; // This is lower than the minimum. - fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002); - fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004); + FakeSystemState::Get()->fake_hardware()->SetMinKernelKeyVersion(0x00010002); + FakeSystemState::Get()->fake_hardware()->SetMinFirmwareKeyVersion(0x00030004); - OmahaRequestParams params(&fake_system_state_); + OmahaRequestParams params; params.set_rollback_allowed(true); + params.set_rollback_allowed_milestones(4); - fake_system_state_.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); InstallPlan install_plan; EXPECT_FALSE(DoTest(in, "", &install_plan)); } @@ -605,13 +881,24 @@ TEST_F(OmahaResponseHandlerActionTest, RollbackNotRollbackTest) { .hash = kPayloadHashHex}); in.is_rollback = false; - OmahaRequestParams params(&fake_system_state_); + const uint32_t current_kernel_max_rollforward = 0xaaaaaaaa; + FakeSystemState::Get()->fake_hardware()->SetMaxKernelKeyRollforward( + current_kernel_max_rollforward); + + OmahaRequestParams params; params.set_rollback_allowed(true); + params.set_rollback_allowed_milestones(4); - fake_system_state_.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); EXPECT_FALSE(install_plan.is_rollback); + + // Max rollforward is not changed for non-rollback cases. + EXPECT_EQ( + current_kernel_max_rollforward, + FakeSystemState::Get()->fake_hardware()->GetMaxKernelKeyRollforward()); + // TODO(crbug/783998): Add support for firmware when implemented. } TEST_F(OmahaResponseHandlerActionTest, RollbackNotAllowedTest) { @@ -622,25 +909,66 @@ TEST_F(OmahaResponseHandlerActionTest, RollbackNotAllowedTest) { .hash = kPayloadHashHex}); in.is_rollback = true; - OmahaRequestParams params(&fake_system_state_); + OmahaRequestParams params; params.set_rollback_allowed(false); + params.set_rollback_allowed_milestones(4); - fake_system_state_.set_request_params(¶ms); + const uint32_t current_kernel_max_rollforward = 0xaaaaaaaa; + FakeSystemState::Get()->fake_hardware()->SetMaxKernelKeyRollforward( + current_kernel_max_rollforward); + + FakeSystemState::Get()->set_request_params(¶ms); InstallPlan install_plan; EXPECT_FALSE(DoTest(in, "", &install_plan)); + + // This case generates an error so, do not update max rollforward. + EXPECT_EQ( + current_kernel_max_rollforward, + FakeSystemState::Get()->fake_hardware()->GetMaxKernelKeyRollforward()); + // TODO(crbug/783998): Add support for firmware when implemented. +} + +TEST_F(OmahaResponseHandlerActionTest, NormalUpdateWithZeroMilestonesAllowed) { + OmahaResponse in; + in.update_exists = true; + in.packages.push_back({.payload_urls = {"https://RollbackTest"}, + .size = 1, + .hash = kPayloadHashHex}); + in.is_rollback = false; + + OmahaRequestParams params; + params.set_rollback_allowed(true); + params.set_rollback_allowed_milestones(0); + + const uint32_t current_kernel_max_rollforward = 0xaaaaaaaa; + FakeSystemState::Get()->fake_hardware()->SetMaxKernelKeyRollforward( + current_kernel_max_rollforward); + + FakeSystemState::Get()->set_request_params(¶ms); + InstallPlan install_plan; + EXPECT_TRUE(DoTest(in, "", &install_plan)); + + // When allowed_milestones is 0, this is set to infinity. + EXPECT_EQ( + kRollforwardInfinity, + FakeSystemState::Get()->fake_hardware()->GetMaxKernelKeyRollforward()); + // TODO(crbug/783998): Add support for firmware when implemented. } TEST_F(OmahaResponseHandlerActionTest, SystemVersionTest) { OmahaResponse in; in.update_exists = true; in.version = "a.b.c.d"; - in.system_version = "b.c.d.e"; in.packages.push_back({.payload_urls = {"http://package/1"}, .size = 1, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); in.packages.push_back({.payload_urls = {"http://package/2"}, .size = 2, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp2}); in.more_info_url = "http://more/info"; InstallPlan install_plan; EXPECT_TRUE(DoTest(in, "", &install_plan)); @@ -650,8 +978,11 @@ TEST_F(OmahaResponseHandlerActionTest, SystemVersionTest) { EXPECT_EQ(in.packages[1].size, install_plan.payloads[1].size); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); EXPECT_EQ(expected_hash_, install_plan.payloads[1].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[1].app_id, install_plan.payloads[1].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); + EXPECT_EQ(in.packages[1].fp, install_plan.payloads[1].fp); EXPECT_EQ(in.version, install_plan.version); - EXPECT_EQ(in.system_version, install_plan.system_version); } TEST_F(OmahaResponseHandlerActionTest, TestDeferredByPolicy) { @@ -660,12 +991,13 @@ TEST_F(OmahaResponseHandlerActionTest, TestDeferredByPolicy) { in.version = "a.b.c.d"; in.packages.push_back({.payload_urls = {"http://foo/the_update_a.b.c.d.tgz"}, .size = 12, - .hash = kPayloadHashHex}); + .hash = kPayloadHashHex, + .app_id = kPayloadAppId, + .fp = kPayloadFp1}); // Setup the UpdateManager to disallow the update. - FakeClock fake_clock; - MockPolicy* mock_policy = new MockPolicy(&fake_clock); + MockPolicy* mock_policy = new MockPolicy(); FakeUpdateManager* fake_update_manager = - fake_system_state_.fake_update_manager(); + FakeSystemState::Get()->fake_update_manager(); fake_update_manager->set_policy(mock_policy); EXPECT_CALL(*mock_policy, UpdateCanBeApplied(_, _, _, _, _)) .WillOnce( @@ -681,6 +1013,8 @@ TEST_F(OmahaResponseHandlerActionTest, TestDeferredByPolicy) { install_plan = *delegate_.response_handler_action_install_plan_; EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url); EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash); + EXPECT_EQ(in.packages[0].app_id, install_plan.payloads[0].app_id); + EXPECT_EQ(in.packages[0].fp, install_plan.payloads[0].fp); EXPECT_EQ(1U, install_plan.target_slot); EXPECT_EQ(in.version, install_plan.version); } diff --git a/power_manager_android.cc b/cros/omaha_utils.cc index 63a0351e..fc05cb9b 100644 --- a/power_manager_android.cc +++ b/cros/omaha_utils.cc @@ -14,23 +14,24 @@ // limitations under the License. // -#include "update_engine/power_manager_android.h" - -#include <memory> +#include "update_engine/cros/omaha_utils.h" #include <base/logging.h> +#include <base/strings/string_number_conversions.h> namespace chromeos_update_engine { -namespace power_manager { -std::unique_ptr<PowerManagerInterface> CreatePowerManager() { - return std::unique_ptr<PowerManagerInterface>(new PowerManagerAndroid()); +const EolDate kEolDateInvalid = -9999; + +std::string EolDateToString(EolDate eol_date) { + return base::NumberToString(eol_date); } -} // namespace power_manager -bool PowerManagerAndroid::RequestReboot() { - LOG(WARNING) << "PowerManager not implemented."; - return false; +EolDate StringToEolDate(const std::string& eol_date) { + EolDate date = kEolDateInvalid; + if (!base::StringToInt64(eol_date, &date)) + return kEolDateInvalid; + return date; } } // namespace chromeos_update_engine diff --git a/omaha_utils.h b/cros/omaha_utils.h index 86145403..67416353 100644 --- a/omaha_utils.h +++ b/cros/omaha_utils.h @@ -14,27 +14,26 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_OMAHA_UTILS_H_ -#define UPDATE_ENGINE_OMAHA_UTILS_H_ +#ifndef UPDATE_ENGINE_CROS_OMAHA_UTILS_H_ +#define UPDATE_ENGINE_CROS_OMAHA_UTILS_H_ #include <string> namespace chromeos_update_engine { -// The end-of-life status of the device. -enum class EolStatus { - kSupported = 0, - kSecurityOnly, - kEol, -}; +using EolDate = int64_t; -// Returns the string representation of the |eol_status|. -const char* EolStatusToString(EolStatus eol_status); +// |EolDate| indicating an invalid end-of-life date. +extern const EolDate kEolDateInvalid; -// Converts the end-of-life status string to an EolStatus numeric value. In case -// of an invalid string, the default "supported" value will be used instead. -EolStatus StringToEolStatus(const std::string& eol_status); +// Returns the string representation of the |eol_date|. +std::string EolDateToString(EolDate eol_date); + +// Converts the end-of-life date string to an EolDate numeric value. In case +// of an invalid string, the default |kEolDateInvalid| value will be used +// instead. +EolDate StringToEolDate(const std::string& eol_date); } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_OMAHA_UTILS_H_ +#endif // UPDATE_ENGINE_CROS_OMAHA_UTILS_H_ diff --git a/omaha_utils_unittest.cc b/cros/omaha_utils_unittest.cc index 8ceb76bf..f89f6902 100644 --- a/omaha_utils_unittest.cc +++ b/cros/omaha_utils_unittest.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/omaha_utils.h" +#include "update_engine/cros/omaha_utils.h" #include <gtest/gtest.h> #include <vector> @@ -23,20 +23,17 @@ namespace chromeos_update_engine { class OmahaUtilsTest : public ::testing::Test {}; -TEST(OmahaUtilsTest, EolStatusTest) { - EXPECT_EQ(EolStatus::kEol, StringToEolStatus("eol")); - +TEST(OmahaUtilsTest, EolDateTest) { // Supported values are converted back and forth properly. - const std::vector<EolStatus> tests = { - EolStatus::kSupported, EolStatus::kSecurityOnly, EolStatus::kEol}; - for (EolStatus eol_status : tests) { - EXPECT_EQ(eol_status, StringToEolStatus(EolStatusToString(eol_status))) - << "The StringToEolStatus() was " << EolStatusToString(eol_status); + const std::vector<EolDate> tests = {kEolDateInvalid, -1, 0, 1}; + for (EolDate eol_date : tests) { + EXPECT_EQ(eol_date, StringToEolDate(EolDateToString(eol_date))) + << "The StringToEolDate() was " << EolDateToString(eol_date); } // Invalid values are assumed as "supported". - EXPECT_EQ(EolStatus::kSupported, StringToEolStatus("")); - EXPECT_EQ(EolStatus::kSupported, StringToEolStatus("hello, world!")); + EXPECT_EQ(kEolDateInvalid, StringToEolDate("")); + EXPECT_EQ(kEolDateInvalid, StringToEolDate("hello, world!")); } } // namespace chromeos_update_engine diff --git a/p2p_manager.cc b/cros/p2p_manager.cc index 67209086..19e2600e 100644 --- a/p2p_manager.cc +++ b/cros/p2p_manager.cc @@ -23,7 +23,7 @@ #define _BSD_SOURCE #endif -#include "update_engine/p2p_manager.h" +#include "update_engine/cros/p2p_manager.h" #include <errno.h> #include <fcntl.h> @@ -51,6 +51,7 @@ #include <base/strings/stringprintf.h> #include "update_engine/common/subprocess.h" +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" #include "update_engine/update_manager/policy.h" #include "update_engine/update_manager/update_manager.h" @@ -65,7 +66,6 @@ using brillo::MessageLoop; using chromeos_update_manager::EvalStatus; using chromeos_update_manager::Policy; using chromeos_update_manager::UpdateManager; -using std::map; using std::pair; using std::string; using std::unique_ptr; @@ -116,7 +116,6 @@ class ConfigurationImpl : public P2PManager::Configuration { class P2PManagerImpl : public P2PManager { public: P2PManagerImpl(Configuration* configuration, - ClockInterface* clock, UpdateManager* update_manager, const string& file_extension, const int num_files_to_keep, @@ -171,9 +170,6 @@ class P2PManagerImpl : public P2PManager { // Configuration object. unique_ptr<Configuration> configuration_; - // Object for telling the time. - ClockInterface* clock_; - // A pointer to the global Update Manager. UpdateManager* update_manager_; @@ -212,13 +208,11 @@ const char P2PManagerImpl::kP2PExtension[] = ".p2p"; const char P2PManagerImpl::kTmpExtension[] = ".tmp"; P2PManagerImpl::P2PManagerImpl(Configuration* configuration, - ClockInterface* clock, UpdateManager* update_manager, const string& file_extension, const int num_files_to_keep, const TimeDelta& max_file_age) - : clock_(clock), - update_manager_(update_manager), + : update_manager_(update_manager), file_extension_(file_extension), num_files_to_keep_(num_files_to_keep), max_file_age_(max_file_age) { @@ -249,12 +243,12 @@ bool P2PManagerImpl::IsP2PEnabled() { bool P2PManagerImpl::EnsureP2P(bool should_be_running) { int return_code = 0; - string output; + string stderr; may_be_running_ = true; // Unless successful, we must be conservative. vector<string> args = configuration_->GetInitctlArgs(should_be_running); - if (!Subprocess::SynchronousExec(args, &return_code, &output)) { + if (!Subprocess::SynchronousExec(args, &return_code, nullptr, &stderr)) { LOG(ERROR) << "Error spawning " << utils::StringVectorToString(args); return false; } @@ -268,7 +262,7 @@ bool P2PManagerImpl::EnsureP2P(bool should_be_running) { const char* expected_error_message = should_be_running ? "initctl: Job is already running: p2p\n" : "initctl: Unknown instance \n"; - if (output != expected_error_message) + if (stderr != expected_error_message) return false; } @@ -345,8 +339,9 @@ bool P2PManagerImpl::PerformHousekeeping() { // If instructed to keep only files younger than a given age // (|max_file_age_| != 0), delete files satisfying this criteria // right now. Otherwise add it to a list we'll consider for later. - if (clock_ != nullptr && max_file_age_ != TimeDelta() && - clock_->GetWallclockTime() - time > max_file_age_) { + if (max_file_age_ != TimeDelta() && + SystemState::Get()->clock()->GetWallclockTime() - time > + max_file_age_) { if (!DeleteP2PFile(name, "file too old")) deletion_failed = true; } else { @@ -723,13 +718,11 @@ void P2PManagerImpl::OnEnabledStatusChange(EvalStatus status, } P2PManager* P2PManager::Construct(Configuration* configuration, - ClockInterface* clock, UpdateManager* update_manager, const string& file_extension, const int num_files_to_keep, const TimeDelta& max_file_age) { return new P2PManagerImpl(configuration, - clock, update_manager, file_extension, num_files_to_keep, diff --git a/p2p_manager.h b/cros/p2p_manager.h index ef62f0df..bef78063 100644 --- a/p2p_manager.h +++ b/cros/p2p_manager.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_P2P_MANAGER_H_ -#define UPDATE_ENGINE_P2P_MANAGER_H_ +#ifndef UPDATE_ENGINE_CROS_P2P_MANAGER_H_ +#define UPDATE_ENGINE_CROS_P2P_MANAGER_H_ #include <string> #include <vector> @@ -27,7 +27,6 @@ #include <policy/device_policy.h> #include <policy/libpolicy.h> -#include "update_engine/common/clock_interface.h" #include "update_engine/common/prefs_interface.h" #include "update_engine/update_manager/update_manager.h" @@ -174,7 +173,6 @@ class P2PManager { // performing housekeeping (pass zero to allow files of any age). static P2PManager* Construct( Configuration* configuration, - ClockInterface* clock, chromeos_update_manager::UpdateManager* update_manager, const std::string& file_extension, const int num_files_to_keep, @@ -183,4 +181,4 @@ class P2PManager { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_P2P_MANAGER_H_ +#endif // UPDATE_ENGINE_CROS_P2P_MANAGER_H_ diff --git a/p2p_manager_unittest.cc b/cros/p2p_manager_unittest.cc index 5771ec1d..17c48865 100644 --- a/p2p_manager_unittest.cc +++ b/cros/p2p_manager_unittest.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/p2p_manager.h" +#include "update_engine/cros/p2p_manager.h" #include <dirent.h> #include <fcntl.h> @@ -30,8 +30,13 @@ #include <base/bind.h> #include <base/callback.h> #include <base/files/file_util.h> +#if BASE_VER < 780000 // Android #include <base/message_loop/message_loop.h> +#endif // BASE_VER < 780000 #include <base/strings/stringprintf.h> +#if BASE_VER >= 780000 // CrOS +#include <base/task/single_thread_task_executor.h> +#endif // BASE_VER >= 780000 #include <brillo/asynchronous_signal_handler.h> #include <brillo/message_loops/base_message_loop.h> #include <brillo/message_loops/message_loop.h> @@ -41,12 +46,12 @@ #include <policy/libpolicy.h> #include <policy/mock_device_policy.h> -#include "update_engine/common/fake_clock.h" #include "update_engine/common/prefs.h" #include "update_engine/common/subprocess.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" -#include "update_engine/fake_p2p_manager_configuration.h" +#include "update_engine/cros/fake_p2p_manager_configuration.h" +#include "update_engine/cros/fake_system_state.h" #include "update_engine/update_manager/fake_update_manager.h" #include "update_engine/update_manager/mock_policy.h" @@ -67,12 +72,13 @@ namespace chromeos_update_engine { // done. class P2PManagerTest : public testing::Test { protected: - P2PManagerTest() : fake_um_(&fake_clock_) {} - ~P2PManagerTest() override {} + P2PManagerTest() = default; + ~P2PManagerTest() override = default; // Derived from testing::Test. void SetUp() override { loop_.SetAsCurrent(); + FakeSystemState::CreateInstance(); async_signal_handler_.Init(); subprocess_.Init(&async_signal_handler_); test_conf_ = new FakeP2PManagerConfiguration(); @@ -80,27 +86,30 @@ class P2PManagerTest : public testing::Test { // Allocate and install a mock policy implementation in the fake Update // Manager. Note that the FakeUpdateManager takes ownership of the policy // object. - mock_policy_ = new chromeos_update_manager::MockPolicy(&fake_clock_); + mock_policy_ = new chromeos_update_manager::MockPolicy(); fake_um_.set_policy(mock_policy_); // Construct the P2P manager under test. manager_.reset(P2PManager::Construct(test_conf_, - &fake_clock_, &fake_um_, "cros_au", 3, TimeDelta::FromDays(5))); } +#if BASE_VER < 780000 // Android base::MessageLoopForIO base_loop_; brillo::BaseMessageLoop loop_{&base_loop_}; +#else // CrOS + base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO}; + brillo::BaseMessageLoop loop_{base_loop_.task_runner()}; +#endif // BASE_VER < 780000 brillo::AsynchronousSignalHandler async_signal_handler_; Subprocess subprocess_; // The P2PManager::Configuration instance used for testing. FakeP2PManagerConfiguration* test_conf_; - FakeClock fake_clock_; chromeos_update_manager::MockPolicy* mock_policy_ = nullptr; chromeos_update_manager::FakeUpdateManager fake_um_; @@ -139,7 +148,6 @@ TEST_F(P2PManagerTest, HousekeepingCountLimit) { // will be freed. test_conf_ = new FakeP2PManagerConfiguration(); manager_.reset(P2PManager::Construct(test_conf_, - &fake_clock_, &fake_um_, "cros_au", 3, @@ -197,14 +205,14 @@ TEST_F(P2PManagerTest, HousekeepingAgeLimit) { // Set the clock just so files with a timestamp before |cutoff_time| // will be deleted at housekeeping. - fake_clock_.SetWallclockTime(cutoff_time + age_limit); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(cutoff_time + + age_limit); // Specifically pass 0 for |num_files_to_keep| to allow any number of files. // Note that we need to reallocate the test_conf_ member, whose currently // aliased object will be freed. test_conf_ = new FakeP2PManagerConfiguration(); manager_.reset(P2PManager::Construct(test_conf_, - &fake_clock_, &fake_um_, "cros_au", 0 /* num_files_to_keep */, @@ -350,7 +358,7 @@ static bool CreateP2PFile(string p2p_dir, // Check that sharing a *new* file works. TEST_F(P2PManagerTest, ShareFile) { - const int kP2PTestFileSize = 1000 * 1000; // 1 MB + const int kP2PTestFileSize = 1000 * 8; // 8 KB EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize)); EXPECT_EQ(manager_->FileGetPath("foo"), @@ -369,7 +377,7 @@ TEST_F(P2PManagerTest, ShareFile) { // Check that making a shared file visible, does what is expected. TEST_F(P2PManagerTest, MakeFileVisible) { - const int kP2PTestFileSize = 1000 * 1000; // 1 MB + const int kP2PTestFileSize = 1000 * 8; // 8 KB // First, check that it's not visible. manager_->FileShare("foo", kP2PTestFileSize); diff --git a/payload_state.cc b/cros/payload_state.cc index 3ba63914..d417ef58 100644 --- a/payload_state.cc +++ b/cros/payload_state.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/payload_state.h" +#include "update_engine/cros/payload_state.h" #include <algorithm> #include <string> @@ -29,14 +29,15 @@ #include "update_engine/common/constants.h" #include "update_engine/common/error_code_utils.h" #include "update_engine/common/hardware_interface.h" +#include "update_engine/common/metrics_reporter_interface.h" #include "update_engine/common/prefs.h" +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" -#include "update_engine/connection_manager_interface.h" -#include "update_engine/metrics_reporter_interface.h" +#include "update_engine/cros/connection_manager_interface.h" +#include "update_engine/cros/omaha_request_params.h" +#include "update_engine/cros/update_attempter.h" #include "update_engine/metrics_utils.h" -#include "update_engine/omaha_request_params.h" #include "update_engine/payload_consumer/install_plan.h" -#include "update_engine/system_state.h" using base::Time; using base::TimeDelta; @@ -60,6 +61,8 @@ static const uint64_t kUptimeResolution = 1; PayloadState::PayloadState() : prefs_(nullptr), + powerwash_safe_prefs_(nullptr), + excluder_(nullptr), using_p2p_for_downloading_(false), p2p_num_attempts_(0), payload_attempt_number_(0), @@ -75,10 +78,10 @@ PayloadState::PayloadState() total_bytes_downloaded_[i] = current_bytes_downloaded_[i] = 0; } -bool PayloadState::Initialize(SystemState* system_state) { - system_state_ = system_state; - prefs_ = system_state_->prefs(); - powerwash_safe_prefs_ = system_state_->powerwash_safe_prefs(); +bool PayloadState::Initialize() { + prefs_ = SystemState::Get()->prefs(); + powerwash_safe_prefs_ = SystemState::Get()->powerwash_safe_prefs(); + excluder_ = SystemState::Get()->update_attempter()->GetExcluder(); LoadResponseSignature(); LoadPayloadAttemptNumber(); LoadFullPayloadAttemptNumber(); @@ -193,7 +196,7 @@ void PayloadState::AttemptStarted(AttemptType attempt_type) { attempt_type_ = attempt_type; - ClockInterface* clock = system_state_->clock(); + const auto* clock = SystemState::Get()->clock(); attempt_start_time_boot_ = clock->GetBootTime(); attempt_start_time_monotonic_ = clock->GetMonotonicTime(); attempt_num_bytes_downloaded_ = 0; @@ -202,7 +205,7 @@ void PayloadState::AttemptStarted(AttemptType attempt_type) { ConnectionType network_connection_type; ConnectionTethering tethering; ConnectionManagerInterface* connection_manager = - system_state_->connection_manager(); + SystemState::Get()->connection_manager(); if (!connection_manager->GetConnectionProperties(&network_connection_type, &tethering)) { LOG(ERROR) << "Failed to determine connection type."; @@ -232,7 +235,7 @@ void PayloadState::UpdateRestarted() { void PayloadState::UpdateSucceeded() { // Send the relevant metrics that are tracked in this class to UMA. CalculateUpdateDurationUptime(); - SetUpdateTimestampEnd(system_state_->clock()->GetWallclockTime()); + SetUpdateTimestampEnd(SystemState::Get()->clock()->GetWallclockTime()); switch (attempt_type_) { case AttemptType::kUpdate: @@ -242,7 +245,7 @@ void PayloadState::UpdateSucceeded() { break; case AttemptType::kRollback: - system_state_->metrics_reporter()->ReportRollbackMetrics( + SystemState::Get()->metrics_reporter()->ReportRollbackMetrics( metrics::RollbackResult::kSuccess); break; } @@ -252,7 +255,7 @@ void PayloadState::UpdateSucceeded() { SetNumResponsesSeen(0); SetPayloadIndex(0); - metrics_utils::SetSystemUpdatedMarker(system_state_->clock(), prefs_); + metrics_utils::SetSystemUpdatedMarker(SystemState::Get()->clock(), prefs_); } void PayloadState::UpdateFailed(ErrorCode error) { @@ -275,7 +278,7 @@ void PayloadState::UpdateFailed(ErrorCode error) { break; case AttemptType::kRollback: - system_state_->metrics_reporter()->ReportRollbackMetrics( + SystemState::Get()->metrics_reporter()->ReportRollbackMetrics( metrics::RollbackResult::kFailed); break; } @@ -308,6 +311,7 @@ void PayloadState::UpdateFailed(ErrorCode error) { case ErrorCode::kUnsupportedMinorPayloadVersion: case ErrorCode::kPayloadTimestampError: case ErrorCode::kVerityCalculationError: + ExcludeCurrentPayload(); IncrementUrlIndex(); break; @@ -370,6 +374,7 @@ void PayloadState::UpdateFailed(ErrorCode error) { 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; @@ -403,7 +408,7 @@ bool PayloadState::ShouldBackoffDownload() { << "will happen from local peer (via p2p)."; return false; } - if (system_state_->request_params()->interactive()) { + if (SystemState::Get()->request_params()->interactive()) { LOG(INFO) << "Payload backoff disabled for interactive update checks."; return false; } @@ -419,7 +424,7 @@ bool PayloadState::ShouldBackoffDownload() { } } - if (!system_state_->hardware()->IsOfficialBuild() && + if (!SystemState::Get()->hardware()->IsOfficialBuild() && !prefs_->Exists(kPrefsNoIgnoreBackoff)) { // Backoffs are needed only for official builds. We do not want any delays // or update failures due to backoffs during testing or development. Unless @@ -448,7 +453,7 @@ bool PayloadState::ShouldBackoffDownload() { } void PayloadState::Rollback() { - SetRollbackVersion(system_state_->request_params()->app_version()); + SetRollbackVersion(SystemState::Get()->request_params()->app_version()); AttemptStarted(AttemptType::kRollback); } @@ -458,6 +463,7 @@ void PayloadState::IncrementPayloadAttemptNumber() { } void PayloadState::IncrementFullPayloadAttemptNumber() { + DCHECK(payload_index_ < response_.packages.size()); // Update the payload attempt number for full payloads and the backoff time. if (response_.packages[payload_index_].is_delta) { LOG(INFO) << "Not incrementing payload attempt number for delta payloads"; @@ -470,10 +476,9 @@ void PayloadState::IncrementFullPayloadAttemptNumber() { } void PayloadState::IncrementUrlIndex() { + DCHECK(payload_index_ < candidate_urls_.size()); size_t next_url_index = url_index_ + 1; - size_t max_url_size = 0; - for (const auto& urls : candidate_urls_) - max_url_size = std::max(max_url_size, urls.size()); + size_t max_url_size = candidate_urls_[payload_index_].size(); if (next_url_index < max_url_size) { LOG(INFO) << "Incrementing the URL index for next attempt"; SetUrlIndex(next_url_index); @@ -502,10 +507,33 @@ void PayloadState::IncrementFailureCount() { } else { LOG(INFO) << "Reached max number of failures for Url" << GetUrlIndex() << ". Trying next available URL"; + ExcludeCurrentPayload(); IncrementUrlIndex(); } } +void PayloadState::ExcludeCurrentPayload() { + if (payload_index_ >= response_.packages.size()) { + LOG(INFO) << "Skipping exclusion of the current payload."; + return; + } + const auto& package = response_.packages[payload_index_]; + if (!package.can_exclude) { + LOG(INFO) << "Not excluding as marked non-excludable for package hash=" + << package.hash; + return; + } + auto exclusion_name = utils::GetExclusionName(GetCurrentUrl()); + if (!excluder_->Exclude(exclusion_name)) + LOG(WARNING) << "Failed to exclude " + << " Package Hash=" << package.hash + << " CurrentUrl=" << GetCurrentUrl(); + else + LOG(INFO) << "Excluded " + << " Package Hash=" << package.hash + << " CurrentUrl=" << GetCurrentUrl(); +} + void PayloadState::UpdateBackoffExpiryTime() { if (response_.disable_payload_backoff) { LOG(INFO) << "Resetting backoff expiry time as payload backoff is disabled"; @@ -583,7 +611,7 @@ PayloadType PayloadState::CalculatePayloadType() { return kPayloadTypeDelta; } } - OmahaRequestParams* params = system_state_->request_params(); + OmahaRequestParams* params = SystemState::Get()->request_params(); if (params->delta_okay()) { return kPayloadTypeFull; } @@ -591,10 +619,6 @@ PayloadType PayloadState::CalculatePayloadType() { return kPayloadTypeForcedFull; } -// TODO(zeuthen): Currently we don't report the UpdateEngine.Attempt.* -// metrics if the attempt ends abnormally, e.g. if the update_engine -// process crashes or the device is rebooted. See -// http://crbug.com/357676 void PayloadState::CollectAndReportAttemptMetrics(ErrorCode code) { int attempt_number = GetPayloadAttemptNumber(); @@ -604,7 +628,7 @@ void PayloadState::CollectAndReportAttemptMetrics(ErrorCode code) { int64_t payload_bytes_downloaded = attempt_num_bytes_downloaded_; - ClockInterface* clock = system_state_->clock(); + const auto* clock = SystemState::Get()->clock(); TimeDelta duration = clock->GetBootTime() - attempt_start_time_boot_; TimeDelta duration_uptime = clock->GetMonotonicTime() - attempt_start_time_monotonic_; @@ -649,13 +673,13 @@ void PayloadState::CollectAndReportAttemptMetrics(ErrorCode code) { case metrics::AttemptResult::kAbnormalTermination: case metrics::AttemptResult::kUpdateCanceled: case metrics::AttemptResult::kUpdateSucceededNotActive: + case metrics::AttemptResult::kUpdateSkipped: case metrics::AttemptResult::kNumConstants: case metrics::AttemptResult::kUnset: break; } - system_state_->metrics_reporter()->ReportUpdateAttemptMetrics( - system_state_, + SystemState::Get()->metrics_reporter()->ReportUpdateAttemptMetrics( attempt_number, payload_type, duration, @@ -664,7 +688,7 @@ void PayloadState::CollectAndReportAttemptMetrics(ErrorCode code) { attempt_result, internal_error_code); - system_state_->metrics_reporter()->ReportUpdateAttemptDownloadMetrics( + SystemState::Get()->metrics_reporter()->ReportUpdateAttemptDownloadMetrics( payload_bytes_downloaded, payload_download_speed_bps, download_source, @@ -694,7 +718,8 @@ void PayloadState::ReportAndClearPersistedAttemptMetrics() { if (!attempt_in_progress) return; - system_state_->metrics_reporter() + SystemState::Get() + ->metrics_reporter() ->ReportAbnormallyTerminatedUpdateAttemptMetrics(); ClearPersistedAttemptMetrics(); @@ -758,7 +783,7 @@ void PayloadState::CollectAndReportSuccessfulUpdateMetrics() { int updates_abandoned_count = num_responses_seen_ - 1; - system_state_->metrics_reporter()->ReportSuccessfulUpdateMetrics( + SystemState::Get()->metrics_reporter()->ReportSuccessfulUpdateMetrics( attempt_count, updates_abandoned_count, payload_type, @@ -774,7 +799,7 @@ void PayloadState::CollectAndReportSuccessfulUpdateMetrics() { void PayloadState::UpdateNumReboots() { // We only update the reboot count when the system has been detected to have // been rebooted. - if (!system_state_->system_rebooted()) { + if (!SystemState::Get()->system_rebooted()) { return; } @@ -794,7 +819,7 @@ void PayloadState::ResetPersistedState() { SetUrlFailureCount(0); SetUrlSwitchCount(0); UpdateBackoffExpiryTime(); // This will reset the backoff expiry time. - SetUpdateTimestampStart(system_state_->clock()->GetWallclockTime()); + SetUpdateTimestampStart(SystemState::Get()->clock()->GetWallclockTime()); SetUpdateTimestampEnd(Time()); // Set to null time SetUpdateDurationUptime(TimeDelta::FromSeconds(0)); ResetDownloadSourcesOnNewUpdate(); @@ -806,7 +831,6 @@ void PayloadState::ResetPersistedState() { } void PayloadState::ResetRollbackVersion() { - CHECK(powerwash_safe_prefs_); rollback_version_ = ""; powerwash_safe_prefs_->Delete(kPrefsRollbackVersion); } @@ -855,7 +879,6 @@ string PayloadState::CalculateResponseSignature() { } void PayloadState::LoadResponseSignature() { - CHECK(prefs_); string stored_value; if (prefs_->Exists(kPrefsCurrentResponseSignature) && prefs_->GetString(kPrefsCurrentResponseSignature, &stored_value)) { @@ -864,7 +887,6 @@ void PayloadState::LoadResponseSignature() { } void PayloadState::SetResponseSignature(const string& response_signature) { - CHECK(prefs_); response_signature_ = response_signature; LOG(INFO) << "Current Response Signature = \n" << response_signature_; prefs_->SetString(kPrefsCurrentResponseSignature, response_signature_); @@ -887,7 +909,6 @@ void PayloadState::SetPayloadAttemptNumber(int payload_attempt_number) { void PayloadState::SetFullPayloadAttemptNumber( int full_payload_attempt_number) { - CHECK(prefs_); full_payload_attempt_number_ = full_payload_attempt_number; LOG(INFO) << "Full Payload Attempt Number = " << full_payload_attempt_number_; prefs_->SetInt64(kPrefsFullPayloadAttemptNumber, @@ -895,16 +916,18 @@ void PayloadState::SetFullPayloadAttemptNumber( } void PayloadState::SetPayloadIndex(size_t payload_index) { - CHECK(prefs_); payload_index_ = payload_index; LOG(INFO) << "Payload Index = " << payload_index_; prefs_->SetInt64(kPrefsUpdateStatePayloadIndex, payload_index_); } bool PayloadState::NextPayload() { - if (payload_index_ + 1 >= candidate_urls_.size()) + if (payload_index_ >= candidate_urls_.size()) return false; SetPayloadIndex(payload_index_ + 1); + if (payload_index_ >= candidate_urls_.size()) + return false; + SetUrlIndex(0); return true; } @@ -913,7 +936,6 @@ void PayloadState::LoadUrlIndex() { } void PayloadState::SetUrlIndex(uint32_t url_index) { - CHECK(prefs_); url_index_ = url_index; LOG(INFO) << "Current URL Index = " << url_index_; prefs_->SetInt64(kPrefsCurrentUrlIndex, url_index_); @@ -929,7 +951,6 @@ void PayloadState::LoadScatteringWaitPeriod() { } void PayloadState::SetScatteringWaitPeriod(TimeDelta wait_period) { - CHECK(prefs_); scattering_wait_period_ = wait_period; LOG(INFO) << "Scattering Wait Period (seconds) = " << scattering_wait_period_.InSeconds(); @@ -947,7 +968,6 @@ void PayloadState::LoadStagingWaitPeriod() { } void PayloadState::SetStagingWaitPeriod(TimeDelta wait_period) { - CHECK(prefs_); staging_wait_period_ = wait_period; LOG(INFO) << "Staging Wait Period (days) =" << staging_wait_period_.InDays(); if (staging_wait_period_.InSeconds() > 0) { @@ -963,7 +983,6 @@ void PayloadState::LoadUrlSwitchCount() { } void PayloadState::SetUrlSwitchCount(uint32_t url_switch_count) { - CHECK(prefs_); url_switch_count_ = url_switch_count; LOG(INFO) << "URL Switch Count = " << url_switch_count_; prefs_->SetInt64(kPrefsUrlSwitchCount, url_switch_count_); @@ -974,7 +993,6 @@ void PayloadState::LoadUrlFailureCount() { } void PayloadState::SetUrlFailureCount(uint32_t url_failure_count) { - CHECK(prefs_); url_failure_count_ = url_failure_count; LOG(INFO) << "Current URL (Url" << GetUrlIndex() << ")'s Failure Count = " << url_failure_count_; @@ -982,7 +1000,6 @@ void PayloadState::SetUrlFailureCount(uint32_t url_failure_count) { } void PayloadState::LoadBackoffExpiryTime() { - CHECK(prefs_); int64_t stored_value; if (!prefs_->Exists(kPrefsBackoffExpiryTime)) return; @@ -1001,7 +1018,6 @@ void PayloadState::LoadBackoffExpiryTime() { } void PayloadState::SetBackoffExpiryTime(const Time& new_time) { - CHECK(prefs_); backoff_expiry_time_ = new_time; LOG(INFO) << "Backoff Expiry Time = " << utils::ToString(backoff_expiry_time_); @@ -1011,7 +1027,7 @@ void PayloadState::SetBackoffExpiryTime(const Time& new_time) { TimeDelta PayloadState::GetUpdateDuration() { Time end_time = update_timestamp_end_.is_null() - ? system_state_->clock()->GetWallclockTime() + ? SystemState::Get()->clock()->GetWallclockTime() : update_timestamp_end_; return end_time - update_timestamp_start_; } @@ -1019,10 +1035,7 @@ TimeDelta PayloadState::GetUpdateDuration() { void PayloadState::LoadUpdateTimestampStart() { int64_t stored_value; Time stored_time; - - CHECK(prefs_); - - Time now = system_state_->clock()->GetWallclockTime(); + Time now = SystemState::Get()->clock()->GetWallclockTime(); if (!prefs_->Exists(kPrefsUpdateTimestampStart)) { // The preference missing is not unexpected - in that case, just @@ -1035,7 +1048,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. @@ -1070,8 +1083,6 @@ void PayloadState::LoadUpdateDurationUptime() { int64_t stored_value; TimeDelta stored_delta; - CHECK(prefs_); - if (!prefs_->Exists(kPrefsUpdateDurationUptime)) { // The preference missing is not unexpected - in that case, just // we'll use zero as the delta @@ -1082,7 +1093,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; @@ -1102,14 +1113,12 @@ void PayloadState::LoadNumReboots() { } void PayloadState::LoadRollbackHappened() { - CHECK(powerwash_safe_prefs_); bool rollback_happened = false; powerwash_safe_prefs_->GetBoolean(kPrefsRollbackHappened, &rollback_happened); SetRollbackHappened(rollback_happened); } void PayloadState::SetRollbackHappened(bool rollback_happened) { - CHECK(powerwash_safe_prefs_); LOG(INFO) << "Setting rollback-happened to " << rollback_happened << "."; rollback_happened_ = rollback_happened; if (rollback_happened) { @@ -1121,7 +1130,6 @@ void PayloadState::SetRollbackHappened(bool rollback_happened) { } void PayloadState::LoadRollbackVersion() { - CHECK(powerwash_safe_prefs_); string rollback_version; if (powerwash_safe_prefs_->GetString(kPrefsRollbackVersion, &rollback_version)) { @@ -1130,8 +1138,7 @@ void PayloadState::LoadRollbackVersion() { } void PayloadState::SetRollbackVersion(const string& rollback_version) { - CHECK(powerwash_safe_prefs_); - LOG(INFO) << "Blacklisting version " << rollback_version; + LOG(INFO) << "Excluding version " << rollback_version; rollback_version_ = rollback_version; powerwash_safe_prefs_->SetString(kPrefsRollbackVersion, rollback_version); } @@ -1139,7 +1146,6 @@ void PayloadState::SetRollbackVersion(const string& rollback_version) { void PayloadState::SetUpdateDurationUptimeExtended(const TimeDelta& value, const Time& timestamp, bool use_logging) { - CHECK(prefs_); update_duration_uptime_ = value; update_duration_uptime_timestamp_ = timestamp; prefs_->SetInt64(kPrefsUpdateDurationUptime, @@ -1151,12 +1157,12 @@ void PayloadState::SetUpdateDurationUptimeExtended(const TimeDelta& value, } void PayloadState::SetUpdateDurationUptime(const TimeDelta& value) { - Time now = system_state_->clock()->GetMonotonicTime(); + Time now = SystemState::Get()->clock()->GetMonotonicTime(); SetUpdateDurationUptimeExtended(value, now, true); } void PayloadState::CalculateUpdateDurationUptime() { - Time now = system_state_->clock()->GetMonotonicTime(); + Time now = SystemState::Get()->clock()->GetMonotonicTime(); TimeDelta uptime_since_last_update = now - update_duration_uptime_timestamp_; if (uptime_since_last_update > TimeDelta::FromSeconds(kUptimeResolution)) { @@ -1178,8 +1184,6 @@ void PayloadState::LoadCurrentBytesDownloaded(DownloadSource source) { void PayloadState::SetCurrentBytesDownloaded(DownloadSource source, uint64_t current_bytes_downloaded, bool log) { - CHECK(prefs_); - if (source >= kNumDownloadSources) return; @@ -1201,8 +1205,6 @@ void PayloadState::LoadTotalBytesDownloaded(DownloadSource source) { void PayloadState::SetTotalBytesDownloaded(DownloadSource source, uint64_t total_bytes_downloaded, bool log) { - CHECK(prefs_); - if (source >= kNumDownloadSources) return; @@ -1221,7 +1223,6 @@ void PayloadState::LoadNumResponsesSeen() { } void PayloadState::SetNumResponsesSeen(int num_responses_seen) { - CHECK(prefs_); num_responses_seen_ = num_responses_seen; LOG(INFO) << "Num Responses Seen = " << num_responses_seen_; prefs_->SetInt64(kPrefsNumResponsesSeen, num_responses_seen_); @@ -1230,8 +1231,8 @@ void PayloadState::SetNumResponsesSeen(int num_responses_seen) { void PayloadState::ComputeCandidateUrls() { bool http_url_ok = true; - if (system_state_->hardware()->IsOfficialBuild()) { - const policy::DevicePolicy* policy = system_state_->device_policy(); + if (SystemState::Get()->hardware()->IsOfficialBuild()) { + const policy::DevicePolicy* policy = SystemState::Get()->device_policy(); if (policy && policy->GetHttpDownloadsEnabled(&http_url_ok) && !http_url_ok) LOG(INFO) << "Downloads via HTTP Url are not enabled by device policy"; } else { @@ -1264,12 +1265,14 @@ void PayloadState::UpdateEngineStarted() { // Avoid the UpdateEngineStarted actions if this is not the first time we // run the update engine since reboot. - if (!system_state_->system_rebooted()) + if (!SystemState::Get()->system_rebooted()) return; // Report time_to_reboot if we booted into a new update. metrics_utils::LoadAndReportTimeToReboot( - system_state_->metrics_reporter(), prefs_, system_state_->clock()); + SystemState::Get()->metrics_reporter(), + prefs_, + SystemState::Get()->clock()); prefs_->Delete(kPrefsSystemUpdatedMarker); // Check if it is needed to send metrics about a failed reboot into a new @@ -1294,7 +1297,8 @@ void PayloadState::ReportFailedBootIfNeeded() { // since we successfully booted the new update in that case. If the boot // failed, we will read this value from the same version, so it will always // be compatible. - if (installed_from == system_state_->boot_control()->GetCurrentSlot()) { + if (installed_from == + SystemState::Get()->boot_control()->GetCurrentSlot()) { // A reboot was pending, but the chromebook is again in the same // BootDevice where the update was installed from. int64_t target_attempt; @@ -1305,7 +1309,7 @@ void PayloadState::ReportFailedBootIfNeeded() { } // Report the UMA metric of the current boot failure. - system_state_->metrics_reporter()->ReportFailedUpdateCount( + SystemState::Get()->metrics_reporter()->ReportFailedUpdateCount( target_attempt); } else { prefs_->Delete(kPrefsTargetVersionAttempt); @@ -1336,7 +1340,7 @@ void PayloadState::ExpectRebootInNewVersion(const string& target_version_uid) { prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt + 1); prefs_->SetInt64(kPrefsTargetVersionInstalledFrom, - system_state_->boot_control()->GetCurrentSlot()); + SystemState::Get()->boot_control()->GetCurrentSlot()); } void PayloadState::ResetUpdateStatus() { @@ -1358,7 +1362,6 @@ int PayloadState::GetP2PNumAttempts() { void PayloadState::SetP2PNumAttempts(int value) { p2p_num_attempts_ = value; LOG(INFO) << "p2p Num Attempts = " << p2p_num_attempts_; - CHECK(prefs_); prefs_->SetInt64(kPrefsP2PNumAttempts, value); } @@ -1374,7 +1377,6 @@ void PayloadState::SetP2PFirstAttemptTimestamp(const Time& time) { p2p_first_attempt_timestamp_ = time; LOG(INFO) << "p2p First Attempt Timestamp = " << utils::ToString(p2p_first_attempt_timestamp_); - CHECK(prefs_); int64_t stored_value = time.ToInternalValue(); prefs_->SetInt64(kPrefsP2PFirstAttemptTimestamp, stored_value); } @@ -1387,10 +1389,10 @@ void PayloadState::LoadP2PFirstAttemptTimestamp() { } void PayloadState::P2PNewAttempt() { - CHECK(prefs_); // Set timestamp, if it hasn't been set already if (p2p_first_attempt_timestamp_.is_null()) { - SetP2PFirstAttemptTimestamp(system_state_->clock()->GetWallclockTime()); + SetP2PFirstAttemptTimestamp( + SystemState::Get()->clock()->GetWallclockTime()); } // Increase number of attempts SetP2PNumAttempts(GetP2PNumAttempts() + 1); @@ -1405,7 +1407,7 @@ bool PayloadState::P2PAttemptAllowed() { } if (!p2p_first_attempt_timestamp_.is_null()) { - Time now = system_state_->clock()->GetWallclockTime(); + Time now = SystemState::Get()->clock()->GetWallclockTime(); TimeDelta time_spent_attempting_p2p = now - p2p_first_attempt_timestamp_; if (time_spent_attempting_p2p.InSeconds() < 0) { LOG(ERROR) << "Time spent attempting p2p is negative" diff --git a/payload_state.h b/cros/payload_state.h index 5ef12202..db548655 100644 --- a/payload_state.h +++ b/cros/payload_state.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_PAYLOAD_STATE_H_ -#define UPDATE_ENGINE_PAYLOAD_STATE_H_ +#ifndef UPDATE_ENGINE_CROS_PAYLOAD_STATE_H_ +#define UPDATE_ENGINE_CROS_PAYLOAD_STATE_H_ #include <algorithm> #include <string> @@ -24,9 +24,10 @@ #include <base/time/time.h> #include <gtest/gtest_prod.h> // for FRIEND_TEST +#include "update_engine/common/excluder_interface.h" +#include "update_engine/common/metrics_constants.h" #include "update_engine/common/prefs_interface.h" -#include "update_engine/metrics_constants.h" -#include "update_engine/payload_state_interface.h" +#include "update_engine/cros/payload_state_interface.h" namespace chromeos_update_engine { @@ -47,7 +48,7 @@ class PayloadState : public PayloadStateInterface { // It performs the initial loading of all persisted state into memory and // dumps the initial state for debugging purposes. Note: the other methods // should be called only after calling Initialize on this object. - bool Initialize(SystemState* system_state); + bool Initialize(); // Implementation of PayloadStateInterface methods. void SetResponse(const OmahaResponse& response) override; @@ -156,6 +157,12 @@ class PayloadState : public PayloadStateInterface { FRIEND_TEST(PayloadStateTest, RollbackHappened); FRIEND_TEST(PayloadStateTest, RollbackVersion); FRIEND_TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs); + FRIEND_TEST(PayloadStateTest, NextPayloadResetsUrlIndex); + FRIEND_TEST(PayloadStateTest, ExcludeNoopForNonExcludables); + FRIEND_TEST(PayloadStateTest, ExcludeOnlyCanExcludables); + FRIEND_TEST(PayloadStateTest, IncrementFailureExclusionTest); + FRIEND_TEST(PayloadStateTest, HaltExclusionPostPayloadExhaustion); + FRIEND_TEST(PayloadStateTest, NonInfinitePayloadIndexIncrement); // Helper called when an attempt has begun, is called by // UpdateResumed(), UpdateRestarted() and Rollback(). @@ -180,6 +187,12 @@ class PayloadState : public PayloadStateInterface { // to the next URL and resets the failure count for that URL. void IncrementFailureCount(); + // Excludes the current payload + current candidate URL from being part of + // future updates/retries. Whenever |SetResponse()| or |NextPayload()| decide + // on the initial current URL index and the next payload respectively, it will + // advanced based on exclusions. + void ExcludeCurrentPayload(); + // Updates the backoff expiry time exponentially based on the current // payload attempt number. void UpdateBackoffExpiryTime(); @@ -355,14 +368,14 @@ class PayloadState : public PayloadStateInterface { // check where policy was available. This info is preserved over powerwash. void LoadRollbackHappened(); - // Loads the blacklisted version from our prefs file. + // Loads the excluded version from our prefs file. void LoadRollbackVersion(); - // Blacklists this version from getting AU'd to until we receive a new update + // Excludes this version from getting AU'd to until we receive a new update // response. void SetRollbackVersion(const std::string& rollback_version); - // Clears any blacklisted version. + // Clears any excluded version. void ResetRollbackVersion(); inline uint32_t GetUrlIndex() { @@ -416,9 +429,6 @@ class PayloadState : public PayloadStateInterface { // Get the total size of all payloads. int64_t GetPayloadSize(); - // The global state of the system. - SystemState* system_state_; - // Interface object with which we read/write persisted state. This must // be set by calling the Initialize method before calling any other method. PrefsInterface* prefs_; @@ -428,6 +438,11 @@ class PayloadState : public PayloadStateInterface { // This object persists across powerwashes. PrefsInterface* powerwash_safe_prefs_; + // Interface object with which we determine exclusion decisions for + // payloads/partitions during the update. This must be set by calling the + // Initialize method before calling any other method. + ExcluderInterface* excluder_; + // This is the current response object from Omaha. OmahaResponse response_; @@ -468,10 +483,9 @@ class PayloadState : public PayloadStateInterface { size_t payload_index_ = 0; // The index of the current URL. This type is different from the one in the - // accessor methods because PrefsInterface supports only int64_t but we want + // accessor methods because |PrefsInterface| supports only int64_t but we want // to provide a stronger abstraction of uint32_t. Each update to this value - // is persisted so we resume from the same value in case of a process - // restart. + // is persisted so we resume from the same value in case of a process restart. size_t url_index_; // The count of failures encountered in the current attempt to download using @@ -549,7 +563,7 @@ class PayloadState : public PayloadStateInterface { // forced updates to avoid update-rollback loops. bool rollback_happened_; - // This stores a blacklisted version set as part of rollback. When we rollback + // This stores an excluded version set as part of rollback. When we rollback // we store the version of the os from which we are rolling back from in order // to guarantee that we do not re-update to it on the next au attempt after // reboot. @@ -581,4 +595,4 @@ class PayloadState : public PayloadStateInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_PAYLOAD_STATE_H_ +#endif // UPDATE_ENGINE_CROS_PAYLOAD_STATE_H_ diff --git a/payload_state_interface.h b/cros/payload_state_interface.h index d384a0eb..9ead6503 100644 --- a/payload_state_interface.h +++ b/cros/payload_state_interface.h @@ -14,14 +14,14 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_ -#define UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_ +#ifndef UPDATE_ENGINE_CROS_PAYLOAD_STATE_INTERFACE_H_ +#define UPDATE_ENGINE_CROS_PAYLOAD_STATE_INTERFACE_H_ #include <string> #include "update_engine/common/action_processor.h" #include "update_engine/common/constants.h" -#include "update_engine/omaha_response.h" +#include "update_engine/cros/omaha_response.h" namespace chromeos_update_engine { @@ -212,4 +212,4 @@ class PayloadStateInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_ +#endif // UPDATE_ENGINE_CROS_PAYLOAD_STATE_INTERFACE_H_ diff --git a/payload_state_unittest.cc b/cros/payload_state_unittest.cc index 869c24eb..5478fcae 100644 --- a/payload_state_unittest.cc +++ b/cros/payload_state_unittest.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/payload_state.h" +#include "update_engine/cros/payload_state.h" #include <base/files/file_path.h> #include <base/files/file_util.h> @@ -23,16 +23,16 @@ #include <gtest/gtest.h> #include "update_engine/common/constants.h" -#include "update_engine/common/fake_clock.h" +#include "update_engine/common/excluder_interface.h" #include "update_engine/common/fake_hardware.h" -#include "update_engine/common/fake_prefs.h" +#include "update_engine/common/metrics_reporter_interface.h" +#include "update_engine/common/mock_excluder.h" #include "update_engine/common/mock_prefs.h" #include "update_engine/common/prefs.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" -#include "update_engine/fake_system_state.h" -#include "update_engine/metrics_reporter_interface.h" -#include "update_engine/omaha_request_action.h" +#include "update_engine/cros/fake_system_state.h" +#include "update_engine/cros/omaha_request_action.h" using base::Time; using base::TimeDelta; @@ -44,6 +44,7 @@ using testing::Mock; using testing::NiceMock; using testing::Return; using testing::SetArgPointee; +using testing::StrictMock; namespace chromeos_update_engine { @@ -103,12 +104,18 @@ static void SetupPayloadStateWith2Urls(string hash, EXPECT_EQ(expected_response_sign, stored_response_sign); } -class PayloadStateTest : public ::testing::Test {}; +class PayloadStateTest : public ::testing::Test { + public: + void SetUp() { FakeSystemState::CreateInstance(); } -TEST(PayloadStateTest, SetResponseWorksWithEmptyResponse) { + // TODO(b/171829801): Replace all the |MockPrefs| in this file with + // |FakePrefs| so we don't have to catch every single unimportant mock call. +}; + +TEST_F(PayloadStateTest, SetResponseWorksWithEmptyResponse) { OmahaResponse response; - FakeSystemState fake_system_state; - NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs(); + FakeSystemState::Get()->set_prefs(nullptr); + auto* prefs = FakeSystemState::Get()->mock_prefs(); EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber()); EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0)) .Times(AtLeast(1)); @@ -130,7 +137,7 @@ TEST(PayloadStateTest, SetResponseWorksWithEmptyResponse) { .Times(AtLeast(1)); EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0)).Times(AtLeast(1)); PayloadState payload_state; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); payload_state.SetResponse(response); string stored_response_sign = payload_state.GetResponseSignature(); string expected_response_sign = @@ -143,15 +150,15 @@ TEST(PayloadStateTest, SetResponseWorksWithEmptyResponse) { EXPECT_EQ(1, payload_state.GetNumResponsesSeen()); } -TEST(PayloadStateTest, SetResponseWorksWithSingleUrl) { +TEST_F(PayloadStateTest, SetResponseWorksWithSingleUrl) { OmahaResponse response; response.packages.push_back({.payload_urls = {"https://single.url.test"}, .size = 123456789, .metadata_size = 58123, .metadata_signature = "msign", .hash = "hash"}); - FakeSystemState fake_system_state; - NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs(); + FakeSystemState::Get()->set_prefs(nullptr); + auto* prefs = FakeSystemState::Get()->mock_prefs(); EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber()); EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0)) .Times(AtLeast(1)); @@ -173,7 +180,7 @@ TEST(PayloadStateTest, SetResponseWorksWithSingleUrl) { .Times(AtLeast(1)); EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0)).Times(AtLeast(1)); PayloadState payload_state; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); payload_state.SetResponse(response); string stored_response_sign = payload_state.GetResponseSignature(); string expected_response_sign = @@ -194,7 +201,7 @@ TEST(PayloadStateTest, SetResponseWorksWithSingleUrl) { EXPECT_EQ(1, payload_state.GetNumResponsesSeen()); } -TEST(PayloadStateTest, SetResponseWorksWithMultipleUrls) { +TEST_F(PayloadStateTest, SetResponseWorksWithMultipleUrls) { OmahaResponse response; response.packages.push_back({.payload_urls = {"http://multiple.url.test", "https://multiple.url.test"}, @@ -202,8 +209,8 @@ TEST(PayloadStateTest, SetResponseWorksWithMultipleUrls) { .metadata_size = 558123, .metadata_signature = "metasign", .hash = "rhash"}); - FakeSystemState fake_system_state; - NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs(); + FakeSystemState::Get()->set_prefs(nullptr); + auto* prefs = FakeSystemState::Get()->mock_prefs(); EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber()); EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0)) .Times(AtLeast(1)); @@ -222,7 +229,7 @@ TEST(PayloadStateTest, SetResponseWorksWithMultipleUrls) { EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0)).Times(AtLeast(1)); PayloadState payload_state; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); payload_state.SetResponse(response); string stored_response_sign = payload_state.GetResponseSignature(); string expected_response_sign = @@ -244,10 +251,10 @@ TEST(PayloadStateTest, SetResponseWorksWithMultipleUrls) { EXPECT_EQ(1, payload_state.GetNumResponsesSeen()); } -TEST(PayloadStateTest, CanAdvanceUrlIndexCorrectly) { +TEST_F(PayloadStateTest, CanAdvanceUrlIndexCorrectly) { OmahaResponse response; - FakeSystemState fake_system_state; - NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs(); + FakeSystemState::Get()->set_prefs(nullptr); + auto* prefs = FakeSystemState::Get()->mock_prefs(); PayloadState payload_state; EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber()); @@ -274,7 +281,7 @@ TEST(PayloadStateTest, CanAdvanceUrlIndexCorrectly) { EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0)) .Times(AtLeast(4)); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); // This does a SetResponse which causes all the states to be set to 0 for // the first time. @@ -299,12 +306,11 @@ TEST(PayloadStateTest, CanAdvanceUrlIndexCorrectly) { EXPECT_EQ(3U, payload_state.GetUrlSwitchCount()); } -TEST(PayloadStateTest, NewResponseResetsPayloadState) { +TEST_F(PayloadStateTest, NewResponseResetsPayloadState) { OmahaResponse response; - FakeSystemState fake_system_state; PayloadState payload_state; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); // Set the first response. SetupPayloadStateWith2Urls( @@ -346,12 +352,12 @@ TEST(PayloadStateTest, NewResponseResetsPayloadState) { payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer)); } -TEST(PayloadStateTest, AllCountersGetUpdatedProperlyOnErrorCodesAndEvents) { +TEST_F(PayloadStateTest, AllCountersGetUpdatedProperlyOnErrorCodesAndEvents) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; int progress_bytes = 100; - NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs(); + FakeSystemState::Get()->set_prefs(nullptr); + auto* prefs = FakeSystemState::Get()->mock_prefs(); EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber()); EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0)) @@ -397,7 +403,7 @@ TEST(PayloadStateTest, AllCountersGetUpdatedProperlyOnErrorCodesAndEvents) { .Times(AtLeast(1)); EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0)).Times(AtLeast(1)); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash5873", true, false, &payload_state, &response); @@ -492,11 +498,12 @@ TEST(PayloadStateTest, AllCountersGetUpdatedProperlyOnErrorCodesAndEvents) { EXPECT_FALSE(payload_state.ShouldBackoffDownload()); } -TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulFullDownload) { +TEST_F(PayloadStateTest, + PayloadAttemptNumberIncreasesOnSuccessfulFullDownload) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs(); + FakeSystemState::Get()->set_prefs(nullptr); + auto* prefs = FakeSystemState::Get()->mock_prefs(); EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber()); EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0)) @@ -515,7 +522,7 @@ TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulFullDownload) { EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0)) .Times(AtLeast(1)); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash8593", true, false, &payload_state, &response); @@ -531,11 +538,12 @@ TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulFullDownload) { EXPECT_EQ(0U, payload_state.GetUrlSwitchCount()); } -TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulDeltaDownload) { +TEST_F(PayloadStateTest, + PayloadAttemptNumberIncreasesOnSuccessfulDeltaDownload) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs(); + FakeSystemState::Get()->set_prefs(nullptr); + auto* prefs = FakeSystemState::Get()->mock_prefs(); EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber()); EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0)) @@ -553,7 +561,7 @@ TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulDeltaDownload) { EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0)) .Times(AtLeast(1)); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls("Hash8593", true, true, &payload_state, &response); @@ -568,12 +576,11 @@ TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulDeltaDownload) { EXPECT_EQ(0U, payload_state.GetUrlSwitchCount()); } -TEST(PayloadStateTest, SetResponseResetsInvalidUrlIndex) { +TEST_F(PayloadStateTest, SetResponseResetsInvalidUrlIndex) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash4427", true, false, &payload_state, &response); @@ -589,27 +596,26 @@ TEST(PayloadStateTest, SetResponseResetsInvalidUrlIndex) { EXPECT_EQ(1U, payload_state.GetUrlSwitchCount()); // Now, simulate a corrupted url index on persisted store which gets - // loaded when update_engine restarts. Using a different prefs object - // so as to not bother accounting for the uninteresting calls above. - FakeSystemState fake_system_state2; - NiceMock<MockPrefs>* prefs2 = fake_system_state2.mock_prefs(); - EXPECT_CALL(*prefs2, Exists(_)).WillRepeatedly(Return(true)); - EXPECT_CALL(*prefs2, GetInt64(_, _)).Times(AtLeast(1)); - EXPECT_CALL(*prefs2, GetInt64(kPrefsPayloadAttemptNumber, _)) + // loaded when update_engine restarts. + FakeSystemState::Get()->set_prefs(nullptr); + auto* prefs = FakeSystemState::Get()->mock_prefs(); + EXPECT_CALL(*prefs, Exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*prefs, GetInt64(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*prefs, GetInt64(kPrefsPayloadAttemptNumber, _)) .Times(AtLeast(1)); - EXPECT_CALL(*prefs2, GetInt64(kPrefsFullPayloadAttemptNumber, _)) + EXPECT_CALL(*prefs, GetInt64(kPrefsFullPayloadAttemptNumber, _)) .Times(AtLeast(1)); - EXPECT_CALL(*prefs2, GetInt64(kPrefsCurrentUrlIndex, _)) + EXPECT_CALL(*prefs, GetInt64(kPrefsCurrentUrlIndex, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(true))); - EXPECT_CALL(*prefs2, GetInt64(kPrefsCurrentUrlFailureCount, _)) + EXPECT_CALL(*prefs, GetInt64(kPrefsCurrentUrlFailureCount, _)) .Times(AtLeast(1)); - EXPECT_CALL(*prefs2, GetInt64(kPrefsUrlSwitchCount, _)).Times(AtLeast(1)); + EXPECT_CALL(*prefs, GetInt64(kPrefsUrlSwitchCount, _)).Times(AtLeast(1)); // Note: This will be a different payload object, but the response should // have the same hash as before so as to not trivially reset because the // response was different. We want to specifically test that even if the // response is same, we should reset the state if we find it corrupted. - EXPECT_TRUE(payload_state.Initialize(&fake_system_state2)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash4427", true, false, &payload_state, &response); @@ -622,15 +628,14 @@ TEST(PayloadStateTest, SetResponseResetsInvalidUrlIndex) { EXPECT_EQ(0U, payload_state.GetUrlSwitchCount()); } -TEST(PayloadStateTest, NoBackoffInteractiveChecks) { +TEST_F(PayloadStateTest, NoBackoffInteractiveChecks) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - OmahaRequestParams params(&fake_system_state); - params.Init("", "", true); // interactive = True. - fake_system_state.set_request_params(¶ms); + OmahaRequestParams params; + params.Init("", "", {.interactive = true}); + FakeSystemState::Get()->set_request_params(¶ms); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash6437", true, false, &payload_state, &response); @@ -645,15 +650,14 @@ TEST(PayloadStateTest, NoBackoffInteractiveChecks) { EXPECT_FALSE(payload_state.ShouldBackoffDownload()); } -TEST(PayloadStateTest, NoBackoffForP2PUpdates) { +TEST_F(PayloadStateTest, NoBackoffForP2PUpdates) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - OmahaRequestParams params(&fake_system_state); - params.Init("", "", false); // interactive = False. - fake_system_state.set_request_params(¶ms); + OmahaRequestParams params; + params.Init("", "", {}); + FakeSystemState::Get()->set_request_params(¶ms); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash6437", true, false, &payload_state, &response); @@ -676,12 +680,11 @@ TEST(PayloadStateTest, NoBackoffForP2PUpdates) { EXPECT_TRUE(payload_state.ShouldBackoffDownload()); } -TEST(PayloadStateTest, NoBackoffForDeltaPayloads) { +TEST_F(PayloadStateTest, NoBackoffForDeltaPayloads) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls("Hash6437", true, true, &payload_state, &response); // Simulate a successful download and see that we're ready to download @@ -720,12 +723,11 @@ static void CheckPayloadBackoffState(PayloadState* payload_state, backoff_expiry_time.ToInternalValue()); } -TEST(PayloadStateTest, BackoffPeriodsAreInCorrectRange) { +TEST_F(PayloadStateTest, BackoffPeriodsAreInCorrectRange) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash8939", true, false, &payload_state, &response); @@ -741,13 +743,12 @@ TEST(PayloadStateTest, BackoffPeriodsAreInCorrectRange) { CheckPayloadBackoffState(&payload_state, 10, TimeDelta::FromDays(16)); } -TEST(PayloadStateTest, BackoffLogicCanBeDisabled) { +TEST_F(PayloadStateTest, BackoffLogicCanBeDisabled) { OmahaResponse response; response.disable_payload_backoff = true; PayloadState payload_state; - FakeSystemState fake_system_state; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash8939", true, false, &payload_state, &response); @@ -768,15 +769,14 @@ TEST(PayloadStateTest, BackoffLogicCanBeDisabled) { EXPECT_FALSE(payload_state.ShouldBackoffDownload()); } -TEST(PayloadStateTest, BytesDownloadedMetricsGetAddedToCorrectSources) { +TEST_F(PayloadStateTest, BytesDownloadedMetricsGetAddedToCorrectSources) { OmahaResponse response; response.disable_payload_backoff = true; PayloadState payload_state; - FakeSystemState fake_system_state; uint64_t https_total = 0; uint64_t http_total = 0; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash3286", true, false, &payload_state, &response); EXPECT_EQ(1, payload_state.GetNumResponsesSeen()); @@ -859,7 +859,7 @@ TEST(PayloadStateTest, BytesDownloadedMetricsGetAddedToCorrectSources) { EXPECT_EQ(p2p_total, payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpPeer)); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportSuccessfulUpdateMetrics( 1, _, kPayloadTypeFull, _, _, 314, _, _, _, 3)); @@ -877,12 +877,11 @@ TEST(PayloadStateTest, BytesDownloadedMetricsGetAddedToCorrectSources) { EXPECT_EQ(0, payload_state.GetNumResponsesSeen()); } -TEST(PayloadStateTest, DownloadSourcesUsedIsCorrect) { +TEST_F(PayloadStateTest, DownloadSourcesUsedIsCorrect) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash3286", true, false, &payload_state, &response); @@ -900,7 +899,7 @@ TEST(PayloadStateTest, DownloadSourcesUsedIsCorrect) { int64_t total_bytes[kNumDownloadSources] = {}; total_bytes[kDownloadSourceHttpServer] = num_bytes; - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportSuccessfulUpdateMetrics( _, _, @@ -917,12 +916,11 @@ TEST(PayloadStateTest, DownloadSourcesUsedIsCorrect) { payload_state.UpdateSucceeded(); } -TEST(PayloadStateTest, RestartingUpdateResetsMetrics) { +TEST_F(PayloadStateTest, RestartingUpdateResetsMetrics) { OmahaResponse response; - FakeSystemState fake_system_state; PayloadState payload_state; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); // Set the first response. SetupPayloadStateWith2Urls( @@ -947,25 +945,24 @@ TEST(PayloadStateTest, RestartingUpdateResetsMetrics) { payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer)); } -TEST(PayloadStateTest, NumRebootsIncrementsCorrectly) { - FakeSystemState fake_system_state; - PayloadState payload_state; - - NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs(); +TEST_F(PayloadStateTest, NumRebootsIncrementsCorrectly) { + FakeSystemState::Get()->set_prefs(nullptr); + auto* prefs = FakeSystemState::Get()->mock_prefs(); EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AtLeast(0)); EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 1)).Times(AtLeast(1)); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + PayloadState payload_state; + EXPECT_TRUE(payload_state.Initialize()); payload_state.UpdateRestarted(); EXPECT_EQ(0U, payload_state.GetNumReboots()); - fake_system_state.set_system_rebooted(true); + FakeSystemState::Get()->set_system_rebooted(true); payload_state.UpdateResumed(); // Num reboots should be incremented because system rebooted detected. EXPECT_EQ(1U, payload_state.GetNumReboots()); - fake_system_state.set_system_rebooted(false); + FakeSystemState::Get()->set_system_rebooted(false); payload_state.UpdateResumed(); // Num reboots should now be 1 as reboot was not detected. EXPECT_EQ(1U, payload_state.GetNumReboots()); @@ -975,13 +972,12 @@ TEST(PayloadStateTest, NumRebootsIncrementsCorrectly) { EXPECT_EQ(0U, payload_state.GetNumReboots()); } -TEST(PayloadStateTest, RollbackHappened) { - FakeSystemState fake_system_state; +TEST_F(PayloadStateTest, RollbackHappened) { + FakeSystemState::Get()->set_powerwash_safe_prefs(nullptr); + auto* mock_powerwash_safe_prefs = + FakeSystemState::Get()->mock_powerwash_safe_prefs(); PayloadState payload_state; - - NiceMock<MockPrefs>* mock_powerwash_safe_prefs = - fake_system_state.mock_powerwash_safe_prefs(); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); // Verify pre-conditions are good. EXPECT_FALSE(payload_state.GetRollbackHappened()); @@ -1006,23 +1002,23 @@ TEST(PayloadStateTest, RollbackHappened) { EXPECT_TRUE(payload_state.GetRollbackHappened()); } -TEST(PayloadStateTest, RollbackVersion) { - FakeSystemState fake_system_state; - PayloadState payload_state; +TEST_F(PayloadStateTest, RollbackVersion) { + FakeSystemState::Get()->set_powerwash_safe_prefs(nullptr); + auto* mock_powerwash_safe_prefs = + FakeSystemState::Get()->mock_powerwash_safe_prefs(); - NiceMock<MockPrefs>* mock_powerwash_safe_prefs = - fake_system_state.mock_powerwash_safe_prefs(); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + // Mock out the os version and make sure it's excluded correctly. + string rollback_version = "2345.0.0"; + OmahaRequestParams params; + params.Init(rollback_version, "", {}); + FakeSystemState::Get()->set_request_params(¶ms); + + PayloadState payload_state; + EXPECT_TRUE(payload_state.Initialize()); // Verify pre-conditions are good. EXPECT_TRUE(payload_state.GetRollbackVersion().empty()); - // Mock out the os version and make sure it's blacklisted correctly. - string rollback_version = "2345.0.0"; - OmahaRequestParams params(&fake_system_state); - params.Init(rollback_version, "", false); - fake_system_state.set_request_params(¶ms); - EXPECT_CALL(*mock_powerwash_safe_prefs, SetString(kPrefsRollbackVersion, rollback_version)); payload_state.Rollback(); @@ -1041,37 +1037,33 @@ TEST(PayloadStateTest, RollbackVersion) { // Check that we report only UpdateEngine.Rollback.* metrics in // UpdateSucceeded(). - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportRollbackMetrics(metrics::RollbackResult::kSuccess)) .Times(1); payload_state.UpdateSucceeded(); } -TEST(PayloadStateTest, DurationsAreCorrect) { +TEST_F(PayloadStateTest, DurationsAreCorrect) { OmahaResponse response; response.packages.resize(1); - PayloadState payload_state; - FakeSystemState fake_system_state; - FakeClock fake_clock; - FakePrefs fake_prefs; // Set the clock to a well-known time - 1 second on the wall-clock // and 2 seconds on the monotonic clock - fake_clock.SetWallclockTime(Time::FromInternalValue(1000000)); - fake_clock.SetMonotonicTime(Time::FromInternalValue(2000000)); + auto* fake_clock = FakeSystemState::Get()->fake_clock(); + fake_clock->SetWallclockTime(Time::FromInternalValue(1000000)); + fake_clock->SetMonotonicTime(Time::FromInternalValue(2000000)); - fake_system_state.set_clock(&fake_clock); - fake_system_state.set_prefs(&fake_prefs); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + PayloadState payload_state; + EXPECT_TRUE(payload_state.Initialize()); // Check that durations are correct for a successful update where // time has advanced 7 seconds on the wall clock and 4 seconds on // the monotonic clock. SetupPayloadStateWith2Urls( "Hash8593", true, false, &payload_state, &response); - fake_clock.SetWallclockTime(Time::FromInternalValue(8000000)); - fake_clock.SetMonotonicTime(Time::FromInternalValue(6000000)); + fake_clock->SetWallclockTime(Time::FromInternalValue(8000000)); + fake_clock->SetMonotonicTime(Time::FromInternalValue(6000000)); payload_state.UpdateSucceeded(); EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 7000000); EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 4000000); @@ -1084,8 +1076,8 @@ TEST(PayloadStateTest, DurationsAreCorrect) { // Advance time a bit (10 secs), simulate download progress and // check that durations are updated. - fake_clock.SetWallclockTime(Time::FromInternalValue(18000000)); - fake_clock.SetMonotonicTime(Time::FromInternalValue(16000000)); + fake_clock->SetWallclockTime(Time::FromInternalValue(18000000)); + fake_clock->SetMonotonicTime(Time::FromInternalValue(16000000)); payload_state.DownloadProgress(10); EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 10000000); EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 10000000); @@ -1093,9 +1085,9 @@ TEST(PayloadStateTest, DurationsAreCorrect) { // Now simulate a reboot by resetting monotonic time (to 5000) and // creating a new PayloadState object and check that we load the // durations correctly (e.g. they are the same as before). - fake_clock.SetMonotonicTime(Time::FromInternalValue(5000)); + fake_clock->SetMonotonicTime(Time::FromInternalValue(5000)); PayloadState payload_state2; - EXPECT_TRUE(payload_state2.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state2.Initialize()); payload_state2.SetResponse(response); EXPECT_EQ(payload_state2.GetUpdateDuration().InMicroseconds(), 10000000); EXPECT_EQ(payload_state2.GetUpdateDurationUptime().InMicroseconds(), @@ -1103,65 +1095,62 @@ TEST(PayloadStateTest, DurationsAreCorrect) { // Advance wall-clock by 7 seconds and monotonic clock by 6 seconds // and check that the durations are increased accordingly. - fake_clock.SetWallclockTime(Time::FromInternalValue(25000000)); - fake_clock.SetMonotonicTime(Time::FromInternalValue(6005000)); + fake_clock->SetWallclockTime(Time::FromInternalValue(25000000)); + fake_clock->SetMonotonicTime(Time::FromInternalValue(6005000)); payload_state2.UpdateSucceeded(); EXPECT_EQ(payload_state2.GetUpdateDuration().InMicroseconds(), 17000000); EXPECT_EQ(payload_state2.GetUpdateDurationUptime().InMicroseconds(), 16000000); } -TEST(PayloadStateTest, RebootAfterSuccessfulUpdateTest) { +TEST_F(PayloadStateTest, RebootAfterSuccessfulUpdateTest) { OmahaResponse response; - PayloadState payload_state; - FakeSystemState fake_system_state; - FakeClock fake_clock; - FakePrefs fake_prefs; // Set the clock to a well-known time (t = 30 seconds). - fake_clock.SetMonotonicTime( + auto* fake_clock = FakeSystemState::Get()->fake_clock(); + fake_clock->SetMonotonicTime( Time::FromInternalValue(30 * Time::kMicrosecondsPerSecond)); - fake_system_state.set_clock(&fake_clock); - fake_system_state.set_prefs(&fake_prefs); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + PayloadState payload_state; + EXPECT_TRUE(payload_state.Initialize()); // Make the update succeed. SetupPayloadStateWith2Urls( "Hash8593", true, false, &payload_state, &response); payload_state.UpdateSucceeded(); + auto* fake_prefs = FakeSystemState::Get()->fake_prefs(); // Check that the marker was written. - EXPECT_TRUE(fake_prefs.Exists(kPrefsSystemUpdatedMarker)); + EXPECT_TRUE(fake_prefs->Exists(kPrefsSystemUpdatedMarker)); // Now simulate a reboot and set the wallclock time to a later point // (t = 500 seconds). We do this by using a new PayloadState object // and checking that it emits the right UMA metric with the right // value. - fake_clock.SetMonotonicTime( + fake_clock->SetMonotonicTime( Time::FromInternalValue(500 * Time::kMicrosecondsPerSecond)); PayloadState payload_state2; - EXPECT_TRUE(payload_state2.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state2.Initialize()); // Expect 500 - 30 seconds = 470 seconds ~= 7 min 50 sec - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportTimeToReboot(7)); - fake_system_state.set_system_rebooted(true); + FakeSystemState::Get()->set_system_rebooted(true); payload_state2.UpdateEngineStarted(); // Check that the marker was nuked. - EXPECT_FALSE(fake_prefs.Exists(kPrefsSystemUpdatedMarker)); + EXPECT_FALSE(fake_prefs->Exists(kPrefsSystemUpdatedMarker)); } -TEST(PayloadStateTest, RestartAfterCrash) { +TEST_F(PayloadStateTest, RestartAfterCrash) { PayloadState payload_state; - FakeSystemState fake_system_state; testing::StrictMock<MockMetricsReporter> mock_metrics_reporter; - fake_system_state.set_metrics_reporter(&mock_metrics_reporter); - NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs(); + FakeSystemState::Get()->set_metrics_reporter(&mock_metrics_reporter); + FakeSystemState::Get()->set_prefs(nullptr); + auto* prefs = FakeSystemState::Get()->mock_prefs(); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); // Only the |kPrefsAttemptInProgress| state variable should be read. EXPECT_CALL(*prefs, Exists(_)).Times(0); @@ -1174,83 +1163,75 @@ TEST(PayloadStateTest, RestartAfterCrash) { EXPECT_CALL(*prefs, GetBoolean(kPrefsAttemptInProgress, _)); // Simulate an update_engine restart without a reboot. - fake_system_state.set_system_rebooted(false); + FakeSystemState::Get()->set_system_rebooted(false); payload_state.UpdateEngineStarted(); } -TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsNoReporting) { +TEST_F(PayloadStateTest, AbnormalTerminationAttemptMetricsNoReporting) { PayloadState payload_state; - FakeSystemState fake_system_state; // If there's no marker at startup, ensure we don't report a metric. - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_TRUE(payload_state.Initialize()); + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportAbnormallyTerminatedUpdateAttemptMetrics()) .Times(0); payload_state.UpdateEngineStarted(); } -TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsReported) { - PayloadState payload_state; - FakeSystemState fake_system_state; - FakePrefs fake_prefs; - +TEST_F(PayloadStateTest, AbnormalTerminationAttemptMetricsReported) { // If we have a marker at startup, ensure it's reported and the // marker is then cleared. - fake_system_state.set_prefs(&fake_prefs); - fake_prefs.SetBoolean(kPrefsAttemptInProgress, true); + auto* fake_prefs = FakeSystemState::Get()->fake_prefs(); + fake_prefs->SetBoolean(kPrefsAttemptInProgress, true); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + PayloadState payload_state; + EXPECT_TRUE(payload_state.Initialize()); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportAbnormallyTerminatedUpdateAttemptMetrics()) .Times(1); payload_state.UpdateEngineStarted(); - EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress)); + EXPECT_FALSE(fake_prefs->Exists(kPrefsAttemptInProgress)); } -TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsClearedOnSucceess) { - PayloadState payload_state; - FakeSystemState fake_system_state; - FakePrefs fake_prefs; - +TEST_F(PayloadStateTest, AbnormalTerminationAttemptMetricsClearedOnSucceess) { // Make sure the marker is written and cleared during an attempt and // also that we DO NOT emit the metric (since the attempt didn't end // abnormally). - fake_system_state.set_prefs(&fake_prefs); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + PayloadState payload_state; + EXPECT_TRUE(payload_state.Initialize()); OmahaResponse response; response.packages.resize(1); payload_state.SetResponse(response); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportAbnormallyTerminatedUpdateAttemptMetrics()) .Times(0); + auto* fake_prefs = FakeSystemState::Get()->fake_prefs(); // Attempt not in progress, should be clear. - EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress)); + EXPECT_FALSE(fake_prefs->Exists(kPrefsAttemptInProgress)); payload_state.UpdateRestarted(); // Attempt not in progress, should be set. - EXPECT_TRUE(fake_prefs.Exists(kPrefsAttemptInProgress)); + EXPECT_TRUE(fake_prefs->Exists(kPrefsAttemptInProgress)); payload_state.UpdateSucceeded(); // Attempt not in progress, should be clear. - EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress)); + EXPECT_FALSE(fake_prefs->Exists(kPrefsAttemptInProgress)); } -TEST(PayloadStateTest, CandidateUrlsComputedCorrectly) { +TEST_F(PayloadStateTest, CandidateUrlsComputedCorrectly) { OmahaResponse response; - FakeSystemState fake_system_state; PayloadState payload_state; policy::MockDevicePolicy disable_http_policy; - fake_system_state.set_device_policy(&disable_http_policy); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + FakeSystemState::Get()->set_device_policy(&disable_http_policy); + EXPECT_TRUE(payload_state.Initialize()); // Test with no device policy. Should default to allowing http. EXPECT_CALL(disable_http_policy, GetHttpDownloadsEnabled(_)) @@ -1293,7 +1274,7 @@ TEST(PayloadStateTest, CandidateUrlsComputedCorrectly) { // Now, pretend that the HTTP policy is turned on. We want to make sure // the new policy is honored. policy::MockDevicePolicy enable_http_policy; - fake_system_state.set_device_policy(&enable_http_policy); + FakeSystemState::Get()->set_device_policy(&enable_http_policy); EXPECT_CALL(enable_http_policy, GetHttpDownloadsEnabled(_)) .WillRepeatedly(DoAll(SetArgPointee<0>(true), Return(true))); @@ -1316,93 +1297,86 @@ TEST(PayloadStateTest, CandidateUrlsComputedCorrectly) { EXPECT_EQ(0U, payload_state.GetUrlFailureCount()); } -TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsDelta) { +TEST_F(PayloadStateTest, PayloadTypeMetricWhenTypeIsDelta) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls("Hash6437", true, true, &payload_state, &response); // Simulate a successful download and update. payload_state.DownloadComplete(); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportSuccessfulUpdateMetrics( _, _, kPayloadTypeDelta, _, _, _, _, _, _, _)); payload_state.UpdateSucceeded(); // Mock the request to a request where the delta was disabled but Omaha sends // a delta anyway and test again. - OmahaRequestParams params(&fake_system_state); + OmahaRequestParams params; params.set_delta_okay(false); - fake_system_state.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls("Hash6437", true, true, &payload_state, &response); payload_state.DownloadComplete(); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportSuccessfulUpdateMetrics( _, _, kPayloadTypeDelta, _, _, _, _, _, _, _)); payload_state.UpdateSucceeded(); } -TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsForcedFull) { +TEST_F(PayloadStateTest, PayloadTypeMetricWhenTypeIsForcedFull) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); - SetupPayloadStateWith2Urls( - "Hash6437", true, false, &payload_state, &response); // Mock the request to a request where the delta was disabled. - OmahaRequestParams params(&fake_system_state); + OmahaRequestParams params; params.set_delta_okay(false); - fake_system_state.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); + + EXPECT_TRUE(payload_state.Initialize()); + SetupPayloadStateWith2Urls( + "Hash6437", true, false, &payload_state, &response); // Simulate a successful download and update. payload_state.DownloadComplete(); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportSuccessfulUpdateMetrics( _, _, kPayloadTypeForcedFull, _, _, _, _, _, _, _)); payload_state.UpdateSucceeded(); } -TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsFull) { +TEST_F(PayloadStateTest, PayloadTypeMetricWhenTypeIsFull) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash6437", true, false, &payload_state, &response); // Mock the request to a request where the delta is enabled, although the // result is full. - OmahaRequestParams params(&fake_system_state); + OmahaRequestParams params; params.set_delta_okay(true); - fake_system_state.set_request_params(¶ms); + FakeSystemState::Get()->set_request_params(¶ms); // Simulate a successful download and update. payload_state.DownloadComplete(); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportSuccessfulUpdateMetrics( _, _, kPayloadTypeFull, _, _, _, _, _, _, _)); payload_state.UpdateSucceeded(); } -TEST(PayloadStateTest, RebootAfterUpdateFailedMetric) { - FakeSystemState fake_system_state; +TEST_F(PayloadStateTest, RebootAfterUpdateFailedMetric) { OmahaResponse response; PayloadState payload_state; - FakePrefs fake_prefs; - fake_system_state.set_prefs(&fake_prefs); - - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash3141", true, false, &payload_state, &response); @@ -1412,40 +1386,40 @@ TEST(PayloadStateTest, RebootAfterUpdateFailedMetric) { payload_state.ExpectRebootInNewVersion("Version:12345678"); // Reboot into the same environment to get an UMA metric with a value of 1. - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportFailedUpdateCount(1)); payload_state.ReportFailedBootIfNeeded(); - Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_reporter()); + Mock::VerifyAndClearExpectations( + FakeSystemState::Get()->mock_metrics_reporter()); // Simulate a second update and reboot into the same environment, this should // send a value of 2. payload_state.ExpectRebootInNewVersion("Version:12345678"); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportFailedUpdateCount(2)); payload_state.ReportFailedBootIfNeeded(); - Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_reporter()); + Mock::VerifyAndClearExpectations( + FakeSystemState::Get()->mock_metrics_reporter()); // Simulate a third failed reboot to new version, but this time for a // different payload. This should send a value of 1 this time. payload_state.ExpectRebootInNewVersion("Version:3141592"); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportFailedUpdateCount(1)); payload_state.ReportFailedBootIfNeeded(); - Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_reporter()); + Mock::VerifyAndClearExpectations( + FakeSystemState::Get()->mock_metrics_reporter()); } -TEST(PayloadStateTest, RebootAfterUpdateSucceed) { - FakeSystemState fake_system_state; +TEST_F(PayloadStateTest, RebootAfterUpdateSucceed) { OmahaResponse response; PayloadState payload_state; - FakePrefs fake_prefs; - fake_system_state.set_prefs(&fake_prefs); - - FakeBootControl* fake_boot_control = fake_system_state.fake_boot_control(); + FakeBootControl* fake_boot_control = + FakeSystemState::Get()->fake_boot_control(); fake_boot_control->SetCurrentSlot(0); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash3141", true, false, &payload_state, &response); @@ -1457,7 +1431,7 @@ TEST(PayloadStateTest, RebootAfterUpdateSucceed) { // Change the BootDevice to a different one, no metric should be sent. fake_boot_control->SetCurrentSlot(1); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportFailedUpdateCount(_)) .Times(0); payload_state.ReportFailedBootIfNeeded(); @@ -1468,14 +1442,10 @@ TEST(PayloadStateTest, RebootAfterUpdateSucceed) { payload_state.ReportFailedBootIfNeeded(); } -TEST(PayloadStateTest, RebootAfterCanceledUpdate) { - FakeSystemState fake_system_state; +TEST_F(PayloadStateTest, RebootAfterCanceledUpdate) { OmahaResponse response; PayloadState payload_state; - FakePrefs fake_prefs; - - fake_system_state.set_prefs(&fake_prefs); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash3141", true, false, &payload_state, &response); @@ -1484,7 +1454,7 @@ TEST(PayloadStateTest, RebootAfterCanceledUpdate) { payload_state.UpdateSucceeded(); payload_state.ExpectRebootInNewVersion("Version:12345678"); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportFailedUpdateCount(_)) .Times(0); @@ -1495,15 +1465,11 @@ TEST(PayloadStateTest, RebootAfterCanceledUpdate) { payload_state.ReportFailedBootIfNeeded(); } -TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs) { - FakeSystemState fake_system_state; +TEST_F(PayloadStateTest, UpdateSuccessWithWipedPrefs) { PayloadState payload_state; - FakePrefs fake_prefs; - - fake_system_state.set_prefs(&fake_prefs); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); - EXPECT_CALL(*fake_system_state.mock_metrics_reporter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), ReportFailedUpdateCount(_)) .Times(0); @@ -1511,14 +1477,10 @@ TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs) { payload_state.ReportFailedBootIfNeeded(); } -TEST(PayloadStateTest, DisallowP2PAfterTooManyAttempts) { +TEST_F(PayloadStateTest, DisallowP2PAfterTooManyAttempts) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - FakePrefs fake_prefs; - fake_system_state.set_prefs(&fake_prefs); - - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash8593", true, false, &payload_state, &response); @@ -1532,22 +1494,17 @@ TEST(PayloadStateTest, DisallowP2PAfterTooManyAttempts) { EXPECT_FALSE(payload_state.P2PAttemptAllowed()); } -TEST(PayloadStateTest, DisallowP2PAfterDeadline) { +TEST_F(PayloadStateTest, DisallowP2PAfterDeadline) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - FakeClock fake_clock; - FakePrefs fake_prefs; - - fake_system_state.set_clock(&fake_clock); - fake_system_state.set_prefs(&fake_prefs); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash8593", true, false, &payload_state, &response); // Set the clock to 1 second. Time epoch = Time::FromInternalValue(1000000); - fake_clock.SetWallclockTime(epoch); + auto* fake_clock = FakeSystemState::Get()->fake_clock(); + fake_clock->SetWallclockTime(epoch); // Do an attempt - this will set the timestamp. payload_state.P2PNewAttempt(); @@ -1559,7 +1516,7 @@ TEST(PayloadStateTest, DisallowP2PAfterDeadline) { EXPECT_TRUE(payload_state.P2PAttemptAllowed()); // Set clock to half the deadline - this should work. - fake_clock.SetWallclockTime( + fake_clock->SetWallclockTime( epoch + TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds) / 2); EXPECT_TRUE(payload_state.P2PAttemptAllowed()); @@ -1568,24 +1525,20 @@ TEST(PayloadStateTest, DisallowP2PAfterDeadline) { EXPECT_EQ(epoch, payload_state.GetP2PFirstAttemptTimestamp()); // Set clock to _just_ before the deadline - this should work. - fake_clock.SetWallclockTime( + fake_clock->SetWallclockTime( epoch + TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds - 1)); EXPECT_TRUE(payload_state.P2PAttemptAllowed()); // Set clock to _just_ after the deadline - this should not work. - fake_clock.SetWallclockTime( + fake_clock->SetWallclockTime( epoch + TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds + 1)); EXPECT_FALSE(payload_state.P2PAttemptAllowed()); } -TEST(PayloadStateTest, P2PStateVarsInitialValue) { +TEST_F(PayloadStateTest, P2PStateVarsInitialValue) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - FakePrefs fake_prefs; - - fake_system_state.set_prefs(&fake_prefs); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash8593", true, false, &payload_state, &response); @@ -1594,21 +1547,16 @@ TEST(PayloadStateTest, P2PStateVarsInitialValue) { EXPECT_EQ(0, payload_state.GetP2PNumAttempts()); } -TEST(PayloadStateTest, P2PStateVarsArePersisted) { +TEST_F(PayloadStateTest, P2PStateVarsArePersisted) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - FakeClock fake_clock; - FakePrefs fake_prefs; - fake_system_state.set_clock(&fake_clock); - fake_system_state.set_prefs(&fake_prefs); - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash8593", true, false, &payload_state, &response); // Set the clock to something known. Time time = Time::FromInternalValue(12345); - fake_clock.SetWallclockTime(time); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(time); // New p2p attempt - as a side-effect this will update the p2p state vars. payload_state.P2PNewAttempt(); @@ -1618,27 +1566,21 @@ TEST(PayloadStateTest, P2PStateVarsArePersisted) { // Now create a new PayloadState and check that it loads the state // vars correctly. PayloadState payload_state2; - EXPECT_TRUE(payload_state2.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state2.Initialize()); EXPECT_EQ(1, payload_state2.GetP2PNumAttempts()); EXPECT_EQ(time, payload_state2.GetP2PFirstAttemptTimestamp()); } -TEST(PayloadStateTest, P2PStateVarsAreClearedOnNewResponse) { +TEST_F(PayloadStateTest, P2PStateVarsAreClearedOnNewResponse) { OmahaResponse response; PayloadState payload_state; - FakeSystemState fake_system_state; - FakeClock fake_clock; - FakePrefs fake_prefs; - fake_system_state.set_clock(&fake_clock); - fake_system_state.set_prefs(&fake_prefs); - - EXPECT_TRUE(payload_state.Initialize(&fake_system_state)); + EXPECT_TRUE(payload_state.Initialize()); SetupPayloadStateWith2Urls( "Hash8593", true, false, &payload_state, &response); // Set the clock to something known. Time time = Time::FromInternalValue(12345); - fake_clock.SetWallclockTime(time); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(time); // New p2p attempt - as a side-effect this will update the p2p state vars. payload_state.P2PNewAttempt(); @@ -1655,4 +1597,162 @@ TEST(PayloadStateTest, P2PStateVarsAreClearedOnNewResponse) { EXPECT_EQ(null_time, payload_state.GetP2PFirstAttemptTimestamp()); } +TEST_F(PayloadStateTest, NextPayloadResetsUrlIndex) { + PayloadState payload_state; + StrictMock<MockExcluder> mock_excluder; + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetExcluder()) + .WillOnce(Return(&mock_excluder)); + EXPECT_TRUE(payload_state.Initialize()); + + OmahaResponse response; + response.packages.push_back( + {.payload_urls = {"http://test1a", "http://test2a"}, + .size = 123456789, + .metadata_size = 58123, + .metadata_signature = "msign", + .hash = "hash"}); + response.packages.push_back({.payload_urls = {"http://test1b"}, + .size = 123456789, + .metadata_size = 58123, + .metadata_signature = "msign", + .hash = "hash"}); + payload_state.SetResponse(response); + + EXPECT_EQ(payload_state.GetCurrentUrl(), "http://test1a"); + payload_state.IncrementUrlIndex(); + EXPECT_EQ(payload_state.GetCurrentUrl(), "http://test2a"); + + EXPECT_TRUE(payload_state.NextPayload()); + EXPECT_EQ(payload_state.GetCurrentUrl(), "http://test1b"); +} + +TEST_F(PayloadStateTest, ExcludeNoopForNonExcludables) { + PayloadState payload_state; + StrictMock<MockExcluder> mock_excluder; + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetExcluder()) + .WillOnce(Return(&mock_excluder)); + EXPECT_TRUE(payload_state.Initialize()); + + OmahaResponse response; + response.packages.push_back( + {.payload_urls = {"http://test1a", "http://test2a"}, + .size = 123456789, + .metadata_size = 58123, + .metadata_signature = "msign", + .hash = "hash", + .can_exclude = false}); + payload_state.SetResponse(response); + + EXPECT_CALL(mock_excluder, Exclude(_)).Times(0); + payload_state.ExcludeCurrentPayload(); +} + +TEST_F(PayloadStateTest, ExcludeOnlyCanExcludables) { + PayloadState payload_state; + StrictMock<MockExcluder> mock_excluder; + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetExcluder()) + .WillOnce(Return(&mock_excluder)); + EXPECT_TRUE(payload_state.Initialize()); + + OmahaResponse response; + response.packages.push_back( + {.payload_urls = {"http://test1a", "http://test2a"}, + .size = 123456789, + .metadata_size = 58123, + .metadata_signature = "msign", + .hash = "hash", + .can_exclude = true}); + payload_state.SetResponse(response); + + EXPECT_CALL(mock_excluder, Exclude(utils::GetExclusionName("http://test1a"))) + .WillOnce(Return(true)); + payload_state.ExcludeCurrentPayload(); +} + +TEST_F(PayloadStateTest, IncrementFailureExclusionTest) { + PayloadState payload_state; + StrictMock<MockExcluder> mock_excluder; + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetExcluder()) + .WillOnce(Return(&mock_excluder)); + EXPECT_TRUE(payload_state.Initialize()); + + OmahaResponse response; + // Critical package. + response.packages.push_back( + {.payload_urls = {"http://crit-test1a", "http://crit-test2a"}, + .size = 123456789, + .metadata_size = 58123, + .metadata_signature = "msign", + .hash = "hash", + .can_exclude = false}); + // Non-critical package. + response.packages.push_back( + {.payload_urls = {"http://test1a", "http://test2a"}, + .size = 123456789, + .metadata_size = 58123, + .metadata_signature = "msign", + .hash = "hash", + .can_exclude = true}); + response.max_failure_count_per_url = 2; + payload_state.SetResponse(response); + + // Critical package won't be excluded. + // Increment twice as failure count allowed per URL is set to 2. + payload_state.IncrementFailureCount(); + payload_state.IncrementFailureCount(); + + EXPECT_TRUE(payload_state.NextPayload()); + + // First increment failure should not exclude. + payload_state.IncrementFailureCount(); + + // Second increment failure should exclude. + EXPECT_CALL(mock_excluder, Exclude(utils::GetExclusionName("http://test1a"))) + .WillOnce(Return(true)); + payload_state.IncrementFailureCount(); +} + +TEST_F(PayloadStateTest, HaltExclusionPostPayloadExhaustion) { + PayloadState payload_state; + StrictMock<MockExcluder> mock_excluder; + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetExcluder()) + .WillOnce(Return(&mock_excluder)); + EXPECT_TRUE(payload_state.Initialize()); + + OmahaResponse response; + // Non-critical package. + response.packages.push_back( + {.payload_urls = {"http://test1a", "http://test2a"}, + .size = 123456789, + .metadata_size = 58123, + .metadata_signature = "msign", + .hash = "hash", + .can_exclude = true}); + payload_state.SetResponse(response); + + // Exclusion should be called when excluded. + EXPECT_CALL(mock_excluder, Exclude(utils::GetExclusionName("http://test1a"))) + .WillOnce(Return(true)); + payload_state.ExcludeCurrentPayload(); + + // No more paylods to go through. + EXPECT_FALSE(payload_state.NextPayload()); + + // Exclusion should not be called as all |Payload|s are exhausted. + payload_state.ExcludeCurrentPayload(); +} + +TEST_F(PayloadStateTest, NonInfinitePayloadIndexIncrement) { + PayloadState payload_state; + EXPECT_TRUE(payload_state.Initialize()); + + payload_state.SetResponse({}); + + EXPECT_FALSE(payload_state.NextPayload()); + int payload_index = payload_state.payload_index_; + + EXPECT_FALSE(payload_state.NextPayload()); + EXPECT_EQ(payload_index, payload_state.payload_index_); +} + } // namespace chromeos_update_engine diff --git a/common/platform_constants_chromeos.cc b/cros/platform_constants_chromeos.cc index fe94a45a..5a5a5214 100644 --- a/common/platform_constants_chromeos.cc +++ b/cros/platform_constants_chromeos.cc @@ -32,7 +32,6 @@ const char kCACertificatesPath[] = "/usr/share/chromeos-ca-certificates"; const char kOmahaResponseDeadlineFile[] = "/tmp/update-check-response-deadline"; // This directory is wiped during powerwash. const char kNonVolatileDirectory[] = "/var/lib/update_engine"; -const char kPostinstallMountOptions[] = ""; } // namespace constants } // namespace chromeos_update_engine diff --git a/power_manager_chromeos.cc b/cros/power_manager_chromeos.cc index 531d367d..c1a28591 100644 --- a/power_manager_chromeos.cc +++ b/cros/power_manager_chromeos.cc @@ -14,14 +14,14 @@ // limitations under the License. // -#include "update_engine/power_manager_chromeos.h" +#include "update_engine/cros/power_manager_chromeos.h" #include <memory> #include <power_manager/dbus-constants.h> #include <power_manager/dbus-proxies.h> -#include "update_engine/dbus_connection.h" +#include "update_engine/cros/dbus_connection.h" namespace chromeos_update_engine { diff --git a/power_manager_chromeos.h b/cros/power_manager_chromeos.h index eeb14d84..89305083 100644 --- a/power_manager_chromeos.h +++ b/cros/power_manager_chromeos.h @@ -14,13 +14,13 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_POWER_MANAGER_CHROMEOS_H_ -#define UPDATE_ENGINE_POWER_MANAGER_CHROMEOS_H_ +#ifndef UPDATE_ENGINE_CROS_POWER_MANAGER_CHROMEOS_H_ +#define UPDATE_ENGINE_CROS_POWER_MANAGER_CHROMEOS_H_ #include <base/macros.h> #include <power_manager/dbus-proxies.h> -#include "update_engine/power_manager_interface.h" +#include "update_engine/cros/power_manager_interface.h" namespace chromeos_update_engine { @@ -41,4 +41,4 @@ class PowerManagerChromeOS : public PowerManagerInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_POWER_MANAGER_CHROMEOS_H_ +#endif // UPDATE_ENGINE_CROS_POWER_MANAGER_CHROMEOS_H_ diff --git a/power_manager_interface.h b/cros/power_manager_interface.h index 8f776500..1f712d26 100644 --- a/power_manager_interface.h +++ b/cros/power_manager_interface.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_POWER_MANAGER_INTERFACE_H_ -#define UPDATE_ENGINE_POWER_MANAGER_INTERFACE_H_ +#ifndef UPDATE_ENGINE_CROS_POWER_MANAGER_INTERFACE_H_ +#define UPDATE_ENGINE_CROS_POWER_MANAGER_INTERFACE_H_ #include <memory> @@ -44,4 +44,4 @@ std::unique_ptr<PowerManagerInterface> CreatePowerManager(); } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_POWER_MANAGER_INTERFACE_H_ +#endif // UPDATE_ENGINE_CROS_POWER_MANAGER_INTERFACE_H_ diff --git a/real_system_state.cc b/cros/real_system_state.cc index 2f18b4d6..5f89b278 100644 --- a/real_system_state.cc +++ b/cros/real_system_state.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include "update_engine/real_system_state.h" +#include "update_engine/cros/real_system_state.h" #include <memory> #include <string> @@ -25,37 +25,23 @@ #include <base/location.h> #include <base/time/time.h> #include <brillo/message_loops/message_loop.h> -#if USE_CHROME_KIOSK_APP #include <chromeos/dbus/service_constants.h> -#endif // USE_CHROME_KIOSK_APP #include "update_engine/common/boot_control.h" #include "update_engine/common/boot_control_stub.h" #include "update_engine/common/constants.h" -#include "update_engine/common/dlcservice.h" +#include "update_engine/common/dlcservice_interface.h" #include "update_engine/common/hardware.h" #include "update_engine/common/utils.h" -#include "update_engine/metrics_reporter_omaha.h" -#include "update_engine/update_boot_flags_action.h" +#include "update_engine/cros/metrics_reporter_omaha.h" #if USE_DBUS -#include "update_engine/dbus_connection.h" +#include "update_engine/cros/dbus_connection.h" #endif // USE_DBUS #include "update_engine/update_manager/state_factory.h" -using brillo::MessageLoop; - namespace chromeos_update_engine { -RealSystemState::~RealSystemState() { - // Prevent any DBus communication from UpdateAttempter when shutting down the - // daemon. - if (update_attempter_) - update_attempter_->ClearObservers(); -} - bool RealSystemState::Initialize() { - metrics_reporter_.Initialize(); - boot_control_ = boot_control::CreateBootControl(); if (!boot_control_) { LOG(WARNING) << "Unable to create BootControl instance, using stub " @@ -69,15 +55,13 @@ bool RealSystemState::Initialize() { return false; } -#if USE_CHROME_KIOSK_APP kiosk_app_proxy_.reset(new org::chromium::KioskAppServiceInterfaceProxy( DBusConnection::Get()->GetDBus(), chromeos::kKioskAppServiceName)); -#endif // USE_CHROME_KIOSK_APP LOG_IF(INFO, !hardware_->IsNormalBootMode()) << "Booted in dev mode."; LOG_IF(INFO, !hardware_->IsOfficialBuild()) << "Booted non-official build."; - connection_manager_ = connection_manager::CreateConnectionManager(this); + connection_manager_ = connection_manager::CreateConnectionManager(); if (!connection_manager_) { LOG(ERROR) << "Error initializing the ConnectionManagerInterface."; return false; @@ -140,7 +124,7 @@ bool RealSystemState::Initialize() { // will be re-initialized before every request using the actual request // options. This initialization here pre-loads current channel and version, so // the DBus service can access it. - if (!request_params_.Init("", "", false)) { + if (!request_params_.Init("", "", {})) { LOG(WARNING) << "Ignoring OmahaRequestParams initialization error. Some " "features might not work properly."; } @@ -149,8 +133,7 @@ bool RealSystemState::Initialize() { new CertificateChecker(prefs_.get(), &openssl_wrapper_)); certificate_checker_->Init(); - update_attempter_.reset( - new UpdateAttempter(this, certificate_checker_.get())); + update_attempter_.reset(new UpdateAttempter(certificate_checker_.get())); // Initialize the UpdateAttempter before the UpdateManager. update_attempter_->Init(); @@ -158,19 +141,13 @@ bool RealSystemState::Initialize() { // Initialize the Update Manager using the default state factory. chromeos_update_manager::State* um_state = chromeos_update_manager::DefaultStateFactory(&policy_provider_, -#if USE_CHROME_KIOSK_APP - kiosk_app_proxy_.get(), -#else - nullptr, -#endif // USE_CHROME_KIOSK_APP - this); + kiosk_app_proxy_.get()); if (!um_state) { LOG(ERROR) << "Failed to initialize the Update Manager."; return false; } update_manager_.reset(new chromeos_update_manager::UpdateManager( - &clock_, base::TimeDelta::FromSeconds(5), base::TimeDelta::FromHours(12), um_state)); @@ -178,25 +155,23 @@ bool RealSystemState::Initialize() { // The P2P Manager depends on the Update Manager for its initialization. p2p_manager_.reset( P2PManager::Construct(nullptr, - &clock_, update_manager_.get(), "cros_au", kMaxP2PFilesToKeep, base::TimeDelta::FromDays(kMaxP2PFileAgeDays))); - if (!payload_state_.Initialize(this)) { + if (!payload_state_.Initialize()) { LOG(ERROR) << "Failed to initialize the payload state object."; return false; } - // For devices that are not rollback enabled (ie. consumer devices), - // initialize max kernel key version to 0xfffffffe, which is logically - // infinity. - if (policy_provider_.IsConsumerDevice()) { + // For images that are build for debugging purposes like test images + // initialize max kernel key version to 0xfffffffe, which is logical infinity. + if (!hardware_->IsOfficialBuild()) { if (!hardware()->SetMaxKernelKeyRollforward( chromeos_update_manager::kRollforwardInfinity)) { LOG(ERROR) << "Failed to set kernel_max_rollforward to infinity for" - << " consumer devices"; + << " device with test/dev image."; } } @@ -204,43 +179,4 @@ bool RealSystemState::Initialize() { return true; } -bool RealSystemState::StartUpdater() { - // Initiate update checks. - update_attempter_->ScheduleUpdates(); - - auto update_boot_flags_action = - std::make_unique<UpdateBootFlagsAction>(boot_control_.get()); - processor_.EnqueueAction(std::move(update_boot_flags_action)); - // Update boot flags after 45 seconds. - MessageLoop::current()->PostDelayedTask( - FROM_HERE, - base::Bind(&ActionProcessor::StartProcessing, - base::Unretained(&processor_)), - base::TimeDelta::FromSeconds(45)); - - // Broadcast the update engine status on startup to ensure consistent system - // state on crashes. - MessageLoop::current()->PostTask( - FROM_HERE, - base::Bind(&UpdateAttempter::BroadcastStatus, - base::Unretained(update_attempter_.get()))); - - // Run the UpdateEngineStarted() method on |update_attempter|. - MessageLoop::current()->PostTask( - FROM_HERE, - base::Bind(&UpdateAttempter::UpdateEngineStarted, - base::Unretained(update_attempter_.get()))); - return true; -} - -void RealSystemState::AddObserver(ServiceObserverInterface* observer) { - CHECK(update_attempter_.get()); - update_attempter_->AddObserver(observer); -} - -void RealSystemState::RemoveObserver(ServiceObserverInterface* observer) { - CHECK(update_attempter_.get()); - update_attempter_->RemoveObserver(observer); -} - } // namespace chromeos_update_engine diff --git a/real_system_state.h b/cros/real_system_state.h index 47120088..81a5e0e3 100644 --- a/real_system_state.h +++ b/cros/real_system_state.h @@ -14,129 +14,110 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_REAL_SYSTEM_STATE_H_ -#define UPDATE_ENGINE_REAL_SYSTEM_STATE_H_ +#ifndef UPDATE_ENGINE_CROS_REAL_SYSTEM_STATE_H_ +#define UPDATE_ENGINE_CROS_REAL_SYSTEM_STATE_H_ -#include "update_engine/system_state.h" +#include "update_engine/common/system_state.h" #include <memory> #include <set> #include <policy/device_policy.h> - -#if USE_CHROME_KIOSK_APP #include <kiosk-app/dbus-proxies.h> -#endif // USE_CHROME_KIOSK_APP #include "update_engine/certificate_checker.h" #include "update_engine/common/boot_control_interface.h" #include "update_engine/common/clock.h" +#include "update_engine/common/daemon_state_interface.h" #include "update_engine/common/dlcservice_interface.h" #include "update_engine/common/hardware_interface.h" +#include "update_engine/common/metrics_reporter_interface.h" #include "update_engine/common/prefs.h" -#include "update_engine/connection_manager_interface.h" -#include "update_engine/daemon_state_interface.h" -#include "update_engine/metrics_reporter_interface.h" -#include "update_engine/metrics_reporter_omaha.h" -#include "update_engine/p2p_manager.h" -#include "update_engine/payload_state.h" -#include "update_engine/power_manager_interface.h" -#include "update_engine/update_attempter.h" +#include "update_engine/cros/connection_manager_interface.h" +#include "update_engine/cros/metrics_reporter_omaha.h" +#include "update_engine/cros/p2p_manager.h" +#include "update_engine/cros/payload_state.h" +#include "update_engine/cros/power_manager_interface.h" +#include "update_engine/cros/update_attempter.h" #include "update_engine/update_manager/update_manager.h" namespace chromeos_update_engine { // A real implementation of the SystemStateInterface which is // used by the actual product code. -class RealSystemState : public SystemState, public DaemonStateInterface { +class RealSystemState : public SystemState { public: // Constructs all system objects that do not require separate initialization; // see Initialize() below for the remaining ones. RealSystemState() = default; - ~RealSystemState() override; - - // Initializes and sets systems objects that require an initialization - // separately from construction. Returns |true| on success. - bool Initialize(); - - // DaemonStateInterface overrides. - // Start the periodic update attempts. Must be called at the beginning of the - // program to start the periodic update check process. - bool StartUpdater() override; + ~RealSystemState() = default; - void AddObserver(ServiceObserverInterface* observer) override; - void RemoveObserver(ServiceObserverInterface* observer) override; - const std::set<ServiceObserverInterface*>& service_observers() override { - CHECK(update_attempter_.get()); - return update_attempter_->service_observers(); + static void SetInstance(RealSystemState* system_state) { + CHECK(g_pointer_ == nullptr) << "SystemState has been previously set."; + g_pointer_ = system_state; + LOG_IF(FATAL, !system_state->Initialize()) + << "Failed to initialize system state."; } // SystemState overrides. - inline void set_device_policy( - const policy::DevicePolicy* device_policy) override { + void set_device_policy(const policy::DevicePolicy* device_policy) override { device_policy_ = device_policy; } - inline const policy::DevicePolicy* device_policy() override { + const policy::DevicePolicy* device_policy() override { return device_policy_; } - inline BootControlInterface* boot_control() override { - return boot_control_.get(); - } + BootControlInterface* boot_control() override { return boot_control_.get(); } - inline ClockInterface* clock() override { return &clock_; } + ClockInterface* clock() override { return &clock_; } - inline ConnectionManagerInterface* connection_manager() override { + ConnectionManagerInterface* connection_manager() override { return connection_manager_.get(); } - inline HardwareInterface* hardware() override { return hardware_.get(); } + HardwareInterface* hardware() override { return hardware_.get(); } - inline MetricsReporterInterface* metrics_reporter() override { + MetricsReporterInterface* metrics_reporter() override { return &metrics_reporter_; } - inline PrefsInterface* prefs() override { return prefs_.get(); } + PrefsInterface* prefs() override { return prefs_.get(); } - inline PrefsInterface* powerwash_safe_prefs() override { + PrefsInterface* powerwash_safe_prefs() override { return powerwash_safe_prefs_.get(); } - inline PayloadStateInterface* payload_state() override { - return &payload_state_; - } + PayloadStateInterface* payload_state() override { return &payload_state_; } - inline UpdateAttempter* update_attempter() override { + UpdateAttempter* update_attempter() override { return update_attempter_.get(); } - inline OmahaRequestParams* request_params() override { - return &request_params_; - } + OmahaRequestParams* request_params() override { return &request_params_; } - inline P2PManager* p2p_manager() override { return p2p_manager_.get(); } + P2PManager* p2p_manager() override { return p2p_manager_.get(); } - inline chromeos_update_manager::UpdateManager* update_manager() override { + chromeos_update_manager::UpdateManager* update_manager() override { return update_manager_.get(); } - inline PowerManagerInterface* power_manager() override { + PowerManagerInterface* power_manager() override { return power_manager_.get(); } - inline bool system_rebooted() override { return system_rebooted_; } + bool system_rebooted() override { return system_rebooted_; } - inline DlcServiceInterface* dlcservice() override { - return dlcservice_.get(); - } + DlcServiceInterface* dlcservice() override { return dlcservice_.get(); } private: + // Initializes and sets systems objects that require an initialization + // separately from construction. Returns |true| on success. + bool Initialize(); + // Real DBus proxies using the DBus connection. -#if USE_CHROME_KIOSK_APP std::unique_ptr<org::chromium::KioskAppServiceInterfaceProxy> kiosk_app_proxy_; -#endif // USE_CHROME_KIOSK_APP // Interface for the power manager. std::unique_ptr<PowerManagerInterface> power_manager_; @@ -144,7 +125,7 @@ class RealSystemState : public SystemState, public DaemonStateInterface { // Interface for dlcservice. std::unique_ptr<DlcServiceInterface> dlcservice_; - // Interface for the clock. + // Interface for the bootloader control. std::unique_ptr<BootControlInterface> boot_control_; // Interface for the clock. @@ -181,7 +162,7 @@ class RealSystemState : public SystemState, public DaemonStateInterface { std::unique_ptr<UpdateAttempter> update_attempter_; // Common parameters for all Omaha requests. - OmahaRequestParams request_params_{this}; + OmahaRequestParams request_params_; std::unique_ptr<P2PManager> p2p_manager_; @@ -193,10 +174,8 @@ class RealSystemState : public SystemState, public DaemonStateInterface { // rebooted. Important for tracking whether you are running instance of the // update engine on first boot or due to a crash/restart. bool system_rebooted_{false}; - - ActionProcessor processor_; }; } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_REAL_SYSTEM_STATE_H_ +#endif // UPDATE_ENGINE_CROS_REAL_SYSTEM_STATE_H_ diff --git a/cros/requisition_util.cc b/cros/requisition_util.cc new file mode 100644 index 00000000..6296d0bb --- /dev/null +++ b/cros/requisition_util.cc @@ -0,0 +1,69 @@ +// +// 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/cros/requisition_util.h" + +#include <memory> +#include <vector> + +#include <base/files/file_util.h> +#include <base/json/json_file_value_serializer.h> +#include <base/logging.h> +#include <base/strings/string_util.h> + +#include "update_engine/common/subprocess.h" +#include "update_engine/common/utils.h" + +using std::string; +using std::vector; + +namespace { + +constexpr char kOemRequisitionKey[] = "oem_device_requisition"; + +} // namespace + +namespace chromeos_update_engine { + +string ReadDeviceRequisition(const base::FilePath& local_state) { + string requisition; + bool vpd_retval = utils::GetVpdValue(kOemRequisitionKey, &requisition); + + // Some users manually convert non-CfM hardware at enrollment time, so VPD + // value may be missing. So check the Local State JSON as well. + if ((requisition.empty() || !vpd_retval) && base::PathExists(local_state)) { + int error_code; + std::string error_msg; + JSONFileValueDeserializer deserializer(local_state); + std::unique_ptr<base::Value> root = + deserializer.Deserialize(&error_code, &error_msg); + if (!root) { + if (error_code != 0) { + LOG(ERROR) << "Unable to deserialize Local State with exit code: " + << error_code << " and error: " << error_msg; + } + return ""; + } + auto* path = root->FindPath({"enrollment", "device_requisition"}); + if (!path || !path->is_string()) { + return ""; + } + path->GetAsString(&requisition); + } + return requisition; +} + +} // namespace chromeos_update_engine diff --git a/common/dlcservice.h b/cros/requisition_util.h index 9dae5607..6ec47833 100644 --- a/common/dlcservice.h +++ b/cros/requisition_util.h @@ -1,5 +1,5 @@ // -// Copyright (C) 2018 The Android Open Source Project +// 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. @@ -14,19 +14,19 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_COMMON_DLCSERVICE_H_ -#define UPDATE_ENGINE_COMMON_DLCSERVICE_H_ +#ifndef UPDATE_ENGINE_CROS_REQUISITION_UTIL_H_ +#define UPDATE_ENGINE_CROS_REQUISITION_UTIL_H_ -#include <memory> +#include <string> -#include "update_engine/common/dlcservice_interface.h" +#include <base/files/file_path.h> namespace chromeos_update_engine { -// This factory function creates a new DlcServiceInterface instance for the -// current platform. -std::unique_ptr<DlcServiceInterface> CreateDlcService(); +// Checks the VPD and Local State for the device's requisition and returns it, +// or an empty string if the device has no requisition. +std::string ReadDeviceRequisition(const base::FilePath& local_state); } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_COMMON_DLCSERVICE_H_ +#endif // UPDATE_ENGINE_CROS_REQUISITION_UTIL_H_ diff --git a/cros/requisition_util_unittest.cc b/cros/requisition_util_unittest.cc new file mode 100644 index 00000000..269585eb --- /dev/null +++ b/cros/requisition_util_unittest.cc @@ -0,0 +1,94 @@ +// +// 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/cros/requisition_util.h" + +#include <string> + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <gtest/gtest.h> + +#include "update_engine/common/test_utils.h" + +using chromeos_update_engine::test_utils::WriteFileString; +using std::string; + +namespace { + +const char kRemoraJSON[] = + "{\n" + " \"the_list\": [ \"val1\", \"val2\" ],\n" + " \"enrollment\": {\n" + " \"autostart\": true,\n" + " \"can_exit\": false,\n" + " \"device_requisition\": \"remora\"\n" + " },\n" + " \"some_String\": \"1337\",\n" + " \"some_int\": 42\n" + "}\n"; + +const char kNoEnrollmentJSON[] = + "{\n" + " \"the_list\": [ \"val1\", \"val2\" ],\n" + " \"enrollment\": {\n" + " \"autostart\": true,\n" + " \"can_exit\": false,\n" + " \"device_requisition\": \"\"\n" + " },\n" + " \"some_String\": \"1337\",\n" + " \"some_int\": 42\n" + "}\n"; +} // namespace + +namespace chromeos_update_engine { + +class RequisitionUtilTest : public ::testing::Test { + protected: + void SetUp() override { ASSERT_TRUE(root_dir_.CreateUniqueTempDir()); } + + void WriteJsonToFile(const string& json) { + path_ = + base::FilePath(root_dir_.GetPath().value() + "/chronos/Local State"); + ASSERT_TRUE(base::CreateDirectory(path_.DirName())); + ASSERT_TRUE(WriteFileString(path_.value(), json)); + } + + base::ScopedTempDir root_dir_; + base::FilePath path_; +}; + +TEST_F(RequisitionUtilTest, BadJsonReturnsEmpty) { + WriteJsonToFile("this isn't JSON"); + EXPECT_EQ("", ReadDeviceRequisition(path_)); +} + +TEST_F(RequisitionUtilTest, NoFileReturnsEmpty) { + EXPECT_EQ("", ReadDeviceRequisition(path_)); +} + +TEST_F(RequisitionUtilTest, EnrollmentRequisition) { + WriteJsonToFile(kRemoraJSON); + EXPECT_EQ("remora", ReadDeviceRequisition(path_)); +} + +TEST_F(RequisitionUtilTest, BlankEnrollment) { + WriteJsonToFile(kNoEnrollmentJSON); + EXPECT_EQ("", ReadDeviceRequisition(path_)); +} + +} // namespace chromeos_update_engine diff --git a/shill_proxy.cc b/cros/shill_proxy.cc index d398bbab..a3c8543a 100644 --- a/shill_proxy.cc +++ b/cros/shill_proxy.cc @@ -14,9 +14,9 @@ // limitations under the License. // -#include "update_engine/shill_proxy.h" +#include "update_engine/cros/shill_proxy.h" -#include "update_engine/dbus_connection.h" +#include "update_engine/cros/dbus_connection.h" using org::chromium::flimflam::ManagerProxy; using org::chromium::flimflam::ManagerProxyInterface; diff --git a/shill_proxy.h b/cros/shill_proxy.h index 4b466c91..aff428a7 100644 --- a/shill_proxy.h +++ b/cros/shill_proxy.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_SHILL_PROXY_H_ -#define UPDATE_ENGINE_SHILL_PROXY_H_ +#ifndef UPDATE_ENGINE_CROS_SHILL_PROXY_H_ +#define UPDATE_ENGINE_CROS_SHILL_PROXY_H_ #include <memory> #include <string> @@ -25,7 +25,7 @@ #include <dbus/object_path.h> #include <shill/dbus-proxies.h> -#include "update_engine/shill_proxy_interface.h" +#include "update_engine/cros/shill_proxy_interface.h" namespace chromeos_update_engine { @@ -51,4 +51,4 @@ class ShillProxy : public ShillProxyInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_SHILL_PROXY_H_ +#endif // UPDATE_ENGINE_CROS_SHILL_PROXY_H_ diff --git a/shill_proxy_interface.h b/cros/shill_proxy_interface.h index 5f6b44ec..19e81f30 100644 --- a/shill_proxy_interface.h +++ b/cros/shill_proxy_interface.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_SHILL_PROXY_INTERFACE_H_ -#define UPDATE_ENGINE_SHILL_PROXY_INTERFACE_H_ +#ifndef UPDATE_ENGINE_CROS_SHILL_PROXY_INTERFACE_H_ +#define UPDATE_ENGINE_CROS_SHILL_PROXY_INTERFACE_H_ #include <memory> #include <string> @@ -53,4 +53,4 @@ class ShillProxyInterface { } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_SHILL_PROXY_INTERFACE_H_ +#endif // UPDATE_ENGINE_CROS_SHILL_PROXY_INTERFACE_H_ diff --git a/update_attempter.cc b/cros/update_attempter.cc index ee571db6..e0394807 100644 --- a/update_attempter.cc +++ b/cros/update_attempter.cc @@ -14,20 +14,24 @@ // limitations under the License. // -#include "update_engine/update_attempter.h" +#include "update_engine/cros/update_attempter.h" #include <stdint.h> #include <algorithm> +#include <map> #include <memory> #include <string> +#include <unordered_set> #include <utility> #include <vector> #include <base/bind.h> +#include <base/compiler_specific.h> #include <base/files/file_util.h> #include <base/logging.h> #include <base/rand_util.h> +#include <base/strings/string_number_conversions.h> #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> #include <base/time/time.h> @@ -40,26 +44,29 @@ #include "update_engine/certificate_checker.h" #include "update_engine/common/boot_control_interface.h" -#include "update_engine/common/clock_interface.h" #include "update_engine/common/constants.h" #include "update_engine/common/dlcservice_interface.h" +#include "update_engine/common/download_action.h" +#include "update_engine/common/excluder_interface.h" #include "update_engine/common/hardware_interface.h" +#include "update_engine/common/metrics_reporter_interface.h" #include "update_engine/common/platform_constants.h" +#include "update_engine/common/prefs.h" #include "update_engine/common/prefs_interface.h" #include "update_engine/common/subprocess.h" +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" +#include "update_engine/cros/download_action_chromeos.h" +#include "update_engine/cros/omaha_request_action.h" +#include "update_engine/cros/omaha_request_params.h" +#include "update_engine/cros/omaha_response_handler_action.h" +#include "update_engine/cros/omaha_utils.h" +#include "update_engine/cros/p2p_manager.h" +#include "update_engine/cros/payload_state_interface.h" +#include "update_engine/cros/power_manager_interface.h" #include "update_engine/libcurl_http_fetcher.h" -#include "update_engine/metrics_reporter_interface.h" -#include "update_engine/omaha_request_action.h" -#include "update_engine/omaha_request_params.h" -#include "update_engine/omaha_response_handler_action.h" -#include "update_engine/p2p_manager.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/payload_state_interface.h" -#include "update_engine/power_manager_interface.h" -#include "update_engine/system_state.h" #include "update_engine/update_boot_flags_action.h" #include "update_engine/update_manager/policy.h" #include "update_engine/update_manager/policy_utils.h" @@ -68,6 +75,7 @@ using base::Bind; using base::Callback; +using base::FilePath; using base::Time; using base::TimeDelta; using base::TimeTicks; @@ -77,6 +85,7 @@ using chromeos_update_manager::EvalStatus; using chromeos_update_manager::Policy; using chromeos_update_manager::StagingCase; using chromeos_update_manager::UpdateCheckParams; +using std::map; using std::string; using std::vector; using update_engine::UpdateAttemptFlags; @@ -117,14 +126,16 @@ ErrorCode GetErrorCodeForAction(AbstractAction* action, ErrorCode code) { return code; } -UpdateAttempter::UpdateAttempter(SystemState* system_state, - CertificateChecker* cert_checker) +UpdateAttempter::UpdateAttempter(CertificateChecker* cert_checker) : processor_(new ActionProcessor()), - system_state_(system_state), cert_checker_(cert_checker), is_install_(false) {} UpdateAttempter::~UpdateAttempter() { + // Prevent any DBus communication from UpdateAttempter when shutting down the + // daemon. + ClearObservers(); + // CertificateChecker might not be initialized in unittests. if (cert_checker_) cert_checker_->SetObserver(nullptr); @@ -137,48 +148,78 @@ void UpdateAttempter::Init() { // Pulling from the SystemState can only be done after construction, since // this is an aggregate of various objects (such as the UpdateAttempter), // which requires them all to be constructed prior to it being used. - prefs_ = system_state_->prefs(); - omaha_request_params_ = system_state_->request_params(); + prefs_ = SystemState::Get()->prefs(); + omaha_request_params_ = SystemState::Get()->request_params(); if (cert_checker_) cert_checker_->SetObserver(this); // In case of update_engine restart without a reboot we need to restore the // reboot needed state. - if (GetBootTimeAtUpdate(nullptr)) + if (GetBootTimeAtUpdate(nullptr)) { status_ = UpdateStatus::UPDATED_NEED_REBOOT; - else + } else { status_ = UpdateStatus::IDLE; + prefs_->Delete(kPrefsLastFp, {kDlcPrefsSubDir}); + } } bool UpdateAttempter::ScheduleUpdates() { - if (IsUpdateRunningOrScheduled()) + if (IsBusyOrUpdateScheduled()) return false; chromeos_update_manager::UpdateManager* const update_manager = - system_state_->update_manager(); + SystemState::Get()->update_manager(); CHECK(update_manager); Callback<void(EvalStatus, const UpdateCheckParams&)> callback = Bind(&UpdateAttempter::OnUpdateScheduled, base::Unretained(this)); // We limit the async policy request to a reasonably short time, to avoid a // starvation due to a transient bug. - update_manager->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed); + update_manager->AsyncPolicyRequestUpdateCheckAllowed( + callback, &Policy::UpdateCheckAllowed); waiting_for_scheduled_check_ = true; return true; } +bool UpdateAttempter::StartUpdater() { + // Initiate update checks. + ScheduleUpdates(); + + auto update_boot_flags_action = std::make_unique<UpdateBootFlagsAction>( + SystemState::Get()->boot_control()); + aux_processor_.EnqueueAction(std::move(update_boot_flags_action)); + // Update boot flags after 45 seconds. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&ActionProcessor::StartProcessing, + base::Unretained(&aux_processor_)), + base::TimeDelta::FromSeconds(45)); + + // Broadcast the update engine status on startup to ensure consistent system + // state on crashes. + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&UpdateAttempter::BroadcastStatus, base::Unretained(this))); + + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&UpdateAttempter::UpdateEngineStarted, + base::Unretained(this))); + return true; +} + void UpdateAttempter::CertificateChecked(ServerToCheck server_to_check, CertificateCheckResult result) { - system_state_->metrics_reporter()->ReportCertificateCheckMetrics( + SystemState::Get()->metrics_reporter()->ReportCertificateCheckMetrics( server_to_check, result); } bool UpdateAttempter::CheckAndReportDailyMetrics() { int64_t stored_value; - Time now = system_state_->clock()->GetWallclockTime(); - if (system_state_->prefs()->Exists(kPrefsDailyMetricsLastReportedAt) && - system_state_->prefs()->GetInt64(kPrefsDailyMetricsLastReportedAt, - &stored_value)) { + Time now = SystemState::Get()->clock()->GetWallclockTime(); + if (SystemState::Get()->prefs()->Exists(kPrefsDailyMetricsLastReportedAt) && + SystemState::Get()->prefs()->GetInt64(kPrefsDailyMetricsLastReportedAt, + &stored_value)) { Time last_reported_at = Time::FromInternalValue(stored_value); TimeDelta time_reported_since = now - last_reported_at; if (time_reported_since.InSeconds() < 0) { @@ -201,8 +242,8 @@ bool UpdateAttempter::CheckAndReportDailyMetrics() { } LOG(INFO) << "Reporting daily metrics."; - system_state_->prefs()->SetInt64(kPrefsDailyMetricsLastReportedAt, - now.ToInternalValue()); + SystemState::Get()->prefs()->SetInt64(kPrefsDailyMetricsLastReportedAt, + now.ToInternalValue()); ReportOSAge(); @@ -211,10 +252,6 @@ bool UpdateAttempter::CheckAndReportDailyMetrics() { void UpdateAttempter::ReportOSAge() { struct stat sb; - - if (system_state_ == nullptr) - return; - if (stat("/etc/lsb-release", &sb) != 0) { PLOG(ERROR) << "Error getting file status for /etc/lsb-release " << "(Note: this may happen in some unit tests)"; @@ -222,7 +259,7 @@ void UpdateAttempter::ReportOSAge() { } Time lsb_release_timestamp = Time::FromTimeSpec(sb.st_ctim); - Time now = system_state_->clock()->GetWallclockTime(); + Time now = SystemState::Get()->clock()->GetWallclockTime(); TimeDelta age = now - lsb_release_timestamp; if (age.InSeconds() < 0) { LOG(ERROR) << "The OS age (" << utils::FormatTimeDelta(age) @@ -231,16 +268,10 @@ void UpdateAttempter::ReportOSAge() { return; } - system_state_->metrics_reporter()->ReportDailyMetrics(age); + SystemState::Get()->metrics_reporter()->ReportDailyMetrics(age); } -void UpdateAttempter::Update(const string& app_version, - const string& omaha_url, - const string& target_channel, - const string& target_version_prefix, - bool rollback_allowed, - bool obey_proxies, - bool interactive) { +void UpdateAttempter::Update(const UpdateCheckParams& params) { // This is normally called frequently enough so it's appropriate to use as a // hook for reporting daily metrics. // TODO(garnold) This should be hooked to a separate (reliable and consistent) @@ -256,8 +287,7 @@ void UpdateAttempter::Update(const string& app_version, // not performing an update check because of this. LOG(INFO) << "Not updating b/c we already updated and we're waiting for " << "reboot, we'll ping Omaha instead"; - system_state_->metrics_reporter()->ReportUpdateCheckMetrics( - system_state_, + SystemState::Get()->metrics_reporter()->ReportUpdateCheckMetrics( metrics::CheckResult::kRebootPending, metrics::CheckReaction::kUnset, metrics::DownloadErrorCode::kUnset); @@ -269,17 +299,11 @@ void UpdateAttempter::Update(const string& app_version, return; } - if (!CalculateUpdateParams(app_version, - omaha_url, - target_channel, - target_version_prefix, - rollback_allowed, - obey_proxies, - interactive)) { + if (!CalculateUpdateParams(params)) { return; } - BuildUpdateActions(interactive); + BuildUpdateActions(params.interactive); SetStatusAndNotify(UpdateStatus::CHECKING_FOR_UPDATE); @@ -306,8 +330,8 @@ void UpdateAttempter::RefreshDevicePolicy() { else LOG(INFO) << "No device policies/settings present."; - system_state_->set_device_policy(device_policy); - system_state_->p2p_manager()->SetDevicePolicy(device_policy); + SystemState::Get()->set_device_policy(device_policy); + SystemState::Get()->p2p_manager()->SetDevicePolicy(device_policy); } void UpdateAttempter::CalculateP2PParams(bool interactive) { @@ -320,37 +344,31 @@ void UpdateAttempter::CalculateP2PParams(bool interactive) { // (Why would a developer want to opt in? If they are working on the // update_engine or p2p codebases so they can actually test their code.) - if (system_state_ != nullptr) { - if (!system_state_->p2p_manager()->IsP2PEnabled()) { - LOG(INFO) << "p2p is not enabled - disallowing p2p for both" - << " downloading and sharing."; + if (!SystemState::Get()->p2p_manager()->IsP2PEnabled()) { + LOG(INFO) << "p2p is not enabled - disallowing p2p for both" + << " downloading and sharing."; + } else { + // Allow p2p for sharing, even in interactive checks. + use_p2p_for_sharing = true; + if (!interactive) { + LOG(INFO) << "Non-interactive check - allowing p2p for downloading"; + use_p2p_for_downloading = true; } else { - // Allow p2p for sharing, even in interactive checks. - use_p2p_for_sharing = true; - if (!interactive) { - LOG(INFO) << "Non-interactive check - allowing p2p for downloading"; - use_p2p_for_downloading = true; - } else { - LOG(INFO) << "Forcibly disabling use of p2p for downloading " - << "since this update attempt is interactive."; - } + LOG(INFO) << "Forcibly disabling use of p2p for downloading " + << "since this update attempt is interactive."; } } - PayloadStateInterface* const payload_state = system_state_->payload_state(); + PayloadStateInterface* const payload_state = + SystemState::Get()->payload_state(); payload_state->SetUsingP2PForDownloading(use_p2p_for_downloading); payload_state->SetUsingP2PForSharing(use_p2p_for_sharing); } -bool UpdateAttempter::CalculateUpdateParams(const string& app_version, - const string& omaha_url, - const string& target_channel, - const string& target_version_prefix, - bool rollback_allowed, - bool obey_proxies, - bool interactive) { +bool UpdateAttempter::CalculateUpdateParams(const UpdateCheckParams& params) { http_response_code_ = 0; - PayloadStateInterface* const payload_state = system_state_->payload_state(); + PayloadStateInterface* const payload_state = + SystemState::Get()->payload_state(); // Refresh the policy before computing all the update parameters. RefreshDevicePolicy(); @@ -359,19 +377,13 @@ bool UpdateAttempter::CalculateUpdateParams(const string& app_version, // policy is available again. UpdateRollbackHappened(); - // Update the target version prefix. - omaha_request_params_->set_target_version_prefix(target_version_prefix); - - // Set whether rollback is allowed. - omaha_request_params_->set_rollback_allowed(rollback_allowed); - - CalculateStagingParams(interactive); + CalculateStagingParams(params.interactive); // If staging_wait_time_ wasn't set, staging is off, use scattering instead. if (staging_wait_time_.InSeconds() == 0) { - CalculateScatteringParams(interactive); + CalculateScatteringParams(params.interactive); } - CalculateP2PParams(interactive); + CalculateP2PParams(params.interactive); if (payload_state->GetUsingP2PForDownloading() || payload_state->GetUsingP2PForSharing()) { // OK, p2p is to be used - start it and perform housekeeping. @@ -384,34 +396,18 @@ bool UpdateAttempter::CalculateUpdateParams(const string& app_version, } } - if (!omaha_request_params_->Init(app_version, omaha_url, interactive)) { + if (!omaha_request_params_->Init( + forced_app_version_, forced_omaha_url_, params)) { LOG(ERROR) << "Unable to initialize Omaha request params."; return false; } - // Set the target channel, if one was provided. - if (target_channel.empty()) { - LOG(INFO) << "No target channel mandated by policy."; - } else { - LOG(INFO) << "Setting target channel as mandated: " << target_channel; - // Pass in false for powerwash_allowed until we add it to the policy - // protobuf. - string error_message; - if (!omaha_request_params_->SetTargetChannel( - target_channel, false, &error_message)) { - LOG(ERROR) << "Setting the channel failed: " << error_message; - } - - // Since this is the beginning of a new attempt, update the download - // channel. The download channel won't be updated until the next attempt, - // even if target channel changes meanwhile, so that how we'll know if we - // should cancel the current download attempt if there's such a change in - // target channel. - omaha_request_params_->UpdateDownloadChannel(); - } - // Set the DLC module ID list. - omaha_request_params_->set_dlc_module_ids(dlc_module_ids_); - omaha_request_params_->set_is_install(is_install_); + // The function |CalculateDlcParams| makes use of the function |GetAppId| from + // |OmahaRequestParams|, so to ensure that the return from |GetAppId| + // doesn't change, no changes to the values |download_channel_|, + // |image_props_.product_id| and |image_props_.canary_product_id| from + // |omaha_request_params_| shall be made below this line. + CalculateDlcParams(); LOG(INFO) << "target_version_prefix = " << omaha_request_params_->target_version_prefix() @@ -434,7 +430,7 @@ bool UpdateAttempter::CalculateUpdateParams(const string& app_version, << payload_state->GetUsingP2PForSharing(); obeying_proxies_ = true; - if (obey_proxies || proxy_manual_checks_ == 0) { + if (proxy_manual_checks_ == 0) { LOG(INFO) << "forced to obey proxies"; // If forced to obey proxies, every 20th request will not use proxies proxy_manual_checks_++; @@ -458,7 +454,8 @@ void UpdateAttempter::CalculateScatteringParams(bool interactive) { // Take a copy of the old scatter value before we update it, as // we need to update the waiting period if this value changes. TimeDelta old_scatter_factor = scatter_factor_; - const policy::DevicePolicy* device_policy = system_state_->device_policy(); + const policy::DevicePolicy* device_policy = + SystemState::Get()->device_policy(); if (device_policy) { int64_t new_scatter_factor_in_secs = 0; device_policy->GetScatterFactorInSeconds(&new_scatter_factor_in_secs); @@ -472,8 +469,8 @@ void UpdateAttempter::CalculateScatteringParams(bool interactive) { LOG(INFO) << "Scattering disabled since scatter factor is set to 0"; } else if (interactive) { LOG(INFO) << "Scattering disabled as this is an interactive update check"; - } else if (system_state_->hardware()->IsOOBEEnabled() && - !system_state_->hardware()->IsOOBEComplete(nullptr)) { + } else if (SystemState::Get()->hardware()->IsOOBEEnabled() && + !SystemState::Get()->hardware()->IsOOBEComplete(nullptr)) { LOG(INFO) << "Scattering disabled since OOBE is enabled but not complete " "yet"; } else { @@ -575,19 +572,19 @@ void UpdateAttempter::GenerateNewWaitingPeriod() { // fails, we'll still be able to scatter based on our in-memory value. // The persistence only helps in ensuring a good overall distribution // across multiple devices if they tend to reboot too often. - system_state_->payload_state()->SetScatteringWaitPeriod( + SystemState::Get()->payload_state()->SetScatteringWaitPeriod( omaha_request_params_->waiting_period()); } void UpdateAttempter::CalculateStagingParams(bool interactive) { - bool oobe_complete = system_state_->hardware()->IsOOBEEnabled() && - system_state_->hardware()->IsOOBEComplete(nullptr); - auto device_policy = system_state_->device_policy(); + bool oobe_complete = SystemState::Get()->hardware()->IsOOBEEnabled() && + SystemState::Get()->hardware()->IsOOBEComplete(nullptr); + auto device_policy = SystemState::Get()->device_policy(); StagingCase staging_case = StagingCase::kOff; if (device_policy && !interactive && oobe_complete) { staging_wait_time_ = omaha_request_params_->waiting_period(); staging_case = CalculateStagingCase( - device_policy, prefs_, &staging_wait_time_, &staging_schedule_); + device_policy, &staging_wait_time_, &staging_schedule_); } switch (staging_case) { case StagingCase::kOff: @@ -602,8 +599,10 @@ void UpdateAttempter::CalculateStagingParams(bool interactive) { case StagingCase::kNoSavedValue: prefs_->SetInt64(kPrefsWallClockStagingWaitPeriod, staging_wait_time_.InDays()); + FALLTHROUGH; case StagingCase::kSetStagingFromPref: omaha_request_params_->set_waiting_period(staging_wait_time_); + FALLTHROUGH; case StagingCase::kNoAction: // Staging is on, enable wallclock based wait so that its values get used. omaha_request_params_->set_wall_clock_based_wait_enabled(true); @@ -618,61 +617,180 @@ void UpdateAttempter::CalculateStagingParams(bool interactive) { } } +bool UpdateAttempter::ResetDlcPrefs(const string& dlc_id) { + vector<string> failures; + for (auto& sub_key : + {kPrefsPingActive, kPrefsPingLastActive, kPrefsPingLastRollcall}) { + auto key = prefs_->CreateSubKey({kDlcPrefsSubDir, dlc_id, sub_key}); + if (!prefs_->Delete(key)) + failures.emplace_back(sub_key); + } + if (failures.size() != 0) + PLOG(ERROR) << "Failed to delete prefs (" << base::JoinString(failures, ",") + << " for DLC (" << dlc_id << ")."; + + return failures.size() == 0; +} + +void UpdateAttempter::SetPref(const string& pref_key, + const string& pref_value, + const string& payload_id) { + string dlc_id; + if (!omaha_request_params_->GetDlcId(payload_id, &dlc_id)) { + // Not a DLC ID, set fingerprint in perf for platform ID. + prefs_->SetString(pref_key, pref_value); + } else { + // Set fingerprint in pref for DLC ID. + auto key = prefs_->CreateSubKey({kDlcPrefsSubDir, dlc_id, pref_key}); + prefs_->SetString(key, pref_value); + } +} + +bool UpdateAttempter::SetDlcActiveValue(bool is_active, const string& dlc_id) { + if (dlc_id.empty()) { + LOG(ERROR) << "Empty DLC ID passed."; + return false; + } + LOG(INFO) << "Set DLC (" << dlc_id << ") to " + << (is_active ? "Active" : "Inactive"); + if (is_active) { + auto ping_active_key = + prefs_->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive}); + if (!prefs_->SetInt64(ping_active_key, kPingActiveValue)) { + LOG(ERROR) << "Failed to set the value of ping metadata '" + << kPrefsPingActive << "'."; + return false; + } + } else { + return ResetDlcPrefs(dlc_id); + } + return true; +} + +int64_t UpdateAttempter::GetPingMetadata(const string& metadata_key) const { + // The first time a ping is sent, the metadata files containing the values + // sent back by the server still don't exist. A value of -1 is used to + // indicate this. + if (!SystemState::Get()->prefs()->Exists(metadata_key)) + return kPingNeverPinged; + + int64_t value; + if (SystemState::Get()->prefs()->GetInt64(metadata_key, &value)) + return value; + + // Return -2 when the file exists and there is a problem reading from it, or + // the value cannot be converted to an integer. + return kPingUnknownValue; +} + +void UpdateAttempter::CalculateDlcParams() { + // Set the |dlc_ids_| only for an update. This is required to get the + // currently installed DLC(s). + if (!is_install_ && + !SystemState::Get()->dlcservice()->GetDlcsToUpdate(&dlc_ids_)) { + LOG(INFO) << "Failed to retrieve DLC module IDs from dlcservice. Check the " + "state of dlcservice, will not update DLC modules."; + } + map<string, OmahaRequestParams::AppParams> dlc_apps_params; + for (const auto& dlc_id : dlc_ids_) { + OmahaRequestParams::AppParams dlc_params{ + .active_counting_type = OmahaRequestParams::kDateBased, + .name = dlc_id, + .send_ping = false}; + if (is_install_) { + // In some cases, |SetDlcActiveValue| might fail to reset the DLC prefs + // when a DLC is uninstalled. To avoid having stale values from that + // scenario, we reset the metadata values on a new install request. + // Ignore failure to delete stale prefs. + ResetDlcPrefs(dlc_id); + SetDlcActiveValue(true, dlc_id); + } else { + // Only send the ping when the request is to update DLCs. When installing + // DLCs, we don't want to send the ping yet, since the DLCs might fail to + // install or might not really be active yet. + dlc_params.ping_active = kPingActiveValue; + auto ping_active_key = + prefs_->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive}); + if (!prefs_->GetInt64(ping_active_key, &dlc_params.ping_active) || + dlc_params.ping_active != kPingActiveValue) { + dlc_params.ping_active = kPingInactiveValue; + } + auto ping_last_active_key = + prefs_->CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingLastActive}); + dlc_params.ping_date_last_active = GetPingMetadata(ping_last_active_key); + + auto ping_last_rollcall_key = prefs_->CreateSubKey( + {kDlcPrefsSubDir, dlc_id, kPrefsPingLastRollcall}); + dlc_params.ping_date_last_rollcall = + GetPingMetadata(ping_last_rollcall_key); + + dlc_params.send_ping = true; + } + dlc_apps_params[omaha_request_params_->GetDlcAppId(dlc_id)] = dlc_params; + } + omaha_request_params_->set_dlc_apps_params(dlc_apps_params); + omaha_request_params_->set_is_install(is_install_); +} + void UpdateAttempter::BuildUpdateActions(bool interactive) { CHECK(!processor_->IsRunning()); processor_->set_delegate(this); + // The session ID needs to be kept throughout the update flow. The value + // of the session ID will reset/update only when it is a new update flow. + session_id_ = base::GenerateGUID(); + // Actions: auto update_check_fetcher = std::make_unique<LibcurlHttpFetcher>( - GetProxyResolver(), system_state_->hardware()); + GetProxyResolver(), SystemState::Get()->hardware()); update_check_fetcher->set_server_to_check(ServerToCheck::kUpdate); // Try harder to connect to the network, esp when not interactive. // See comment in libcurl_http_fetcher.cc. update_check_fetcher->set_no_network_max_retries(interactive ? 1 : 3); + update_check_fetcher->set_is_update_check(true); auto update_check_action = std::make_unique<OmahaRequestAction>( - system_state_, nullptr, std::move(update_check_fetcher), false); - auto response_handler_action = - std::make_unique<OmahaResponseHandlerAction>(system_state_); - auto update_boot_flags_action = - std::make_unique<UpdateBootFlagsAction>(system_state_->boot_control()); + nullptr, std::move(update_check_fetcher), false, session_id_); + auto response_handler_action = std::make_unique<OmahaResponseHandlerAction>(); + auto update_boot_flags_action = std::make_unique<UpdateBootFlagsAction>( + SystemState::Get()->boot_control()); auto download_started_action = std::make_unique<OmahaRequestAction>( - system_state_, new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted), std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(), - system_state_->hardware()), - false); + SystemState::Get()->hardware()), + false, + session_id_); - LibcurlHttpFetcher* download_fetcher = - new LibcurlHttpFetcher(GetProxyResolver(), system_state_->hardware()); + LibcurlHttpFetcher* download_fetcher = new LibcurlHttpFetcher( + GetProxyResolver(), SystemState::Get()->hardware()); download_fetcher->set_server_to_check(ServerToCheck::kDownload); if (interactive) download_fetcher->set_max_retry_count(kDownloadMaxRetryCountInteractive); - auto download_action = - std::make_unique<DownloadAction>(prefs_, - system_state_->boot_control(), - system_state_->hardware(), - system_state_, - download_fetcher, // passes ownership - interactive); + download_fetcher->SetHeader(kXGoogleUpdateSessionId, session_id_); + auto download_action = std::make_unique<DownloadActionChromeos>( + prefs_, + SystemState::Get()->boot_control(), + SystemState::Get()->hardware(), + download_fetcher, // passes ownership + interactive); download_action->set_delegate(this); auto download_finished_action = std::make_unique<OmahaRequestAction>( - system_state_, new OmahaEvent(OmahaEvent::kTypeUpdateDownloadFinished), std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(), - system_state_->hardware()), - false); - auto filesystem_verifier_action = - std::make_unique<FilesystemVerifierAction>(); + SystemState::Get()->hardware()), + false, + session_id_); + auto filesystem_verifier_action = std::make_unique<FilesystemVerifierAction>( + SystemState::Get()->boot_control()->GetDynamicPartitionControl()); auto update_complete_action = std::make_unique<OmahaRequestAction>( - system_state_, new OmahaEvent(OmahaEvent::kTypeUpdateComplete), std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(), - system_state_->hardware()), - false); + SystemState::Get()->hardware()), + false, + session_id_); auto postinstall_runner_action = std::make_unique<PostinstallRunnerAction>( - system_state_->boot_control(), system_state_->hardware()); + SystemState::Get()->boot_control(), SystemState::Get()->hardware()); postinstall_runner_action->set_delegate(this); // Bond them together. We have to use the leaf-types when calling @@ -706,7 +824,8 @@ bool UpdateAttempter::Rollback(bool powerwash) { // Enterprise-enrolled devices have an empty owner in their device policy. string owner; RefreshDevicePolicy(); - const policy::DevicePolicy* device_policy = system_state_->device_policy(); + const policy::DevicePolicy* device_policy = + SystemState::Get()->device_policy(); if (device_policy && (!device_policy->GetOwner(&owner) || owner.empty())) { LOG(ERROR) << "Enterprise device detected. " << "Cannot perform a powerwash for enterprise devices."; @@ -717,7 +836,7 @@ bool UpdateAttempter::Rollback(bool powerwash) { processor_->set_delegate(this); // Initialize the default request params. - if (!omaha_request_params_->Init("", "", true)) { + if (!omaha_request_params_->Init("", "", {.interactive = true})) { LOG(ERROR) << "Unable to initialize Omaha request params."; return false; } @@ -725,26 +844,26 @@ bool UpdateAttempter::Rollback(bool powerwash) { LOG(INFO) << "Setting rollback options."; install_plan_.reset(new InstallPlan()); install_plan_->target_slot = GetRollbackSlot(); - install_plan_->source_slot = system_state_->boot_control()->GetCurrentSlot(); + install_plan_->source_slot = + SystemState::Get()->boot_control()->GetCurrentSlot(); - TEST_AND_RETURN_FALSE( - install_plan_->LoadPartitionsFromSlots(system_state_->boot_control())); + TEST_AND_RETURN_FALSE(install_plan_->LoadPartitionsFromSlots( + SystemState::Get()->boot_control())); install_plan_->powerwash_required = powerwash; - LOG(INFO) << "Using this install plan:"; install_plan_->Dump(); auto install_plan_action = std::make_unique<InstallPlanAction>(*install_plan_); auto postinstall_runner_action = std::make_unique<PostinstallRunnerAction>( - system_state_->boot_control(), system_state_->hardware()); + SystemState::Get()->boot_control(), SystemState::Get()->hardware()); postinstall_runner_action->set_delegate(this); BondActions(install_plan_action.get(), postinstall_runner_action.get()); processor_->EnqueueAction(std::move(install_plan_action)); processor_->EnqueueAction(std::move(postinstall_runner_action)); // Update the payload state for Rollback. - system_state_->payload_state()->Rollback(); + SystemState::Get()->payload_state()->Rollback(); SetStatusAndNotify(UpdateStatus::ATTEMPTING_ROLLBACK); @@ -761,9 +880,10 @@ bool UpdateAttempter::CanRollback() const { BootControlInterface::Slot UpdateAttempter::GetRollbackSlot() const { LOG(INFO) << "UpdateAttempter::GetRollbackSlot"; - const unsigned int num_slots = system_state_->boot_control()->GetNumSlots(); + const unsigned int num_slots = + SystemState::Get()->boot_control()->GetNumSlots(); const BootControlInterface::Slot current_slot = - system_state_->boot_control()->GetCurrentSlot(); + SystemState::Get()->boot_control()->GetCurrentSlot(); LOG(INFO) << " Installed slots: " << num_slots; LOG(INFO) << " Booted from slot: " @@ -777,7 +897,7 @@ BootControlInterface::Slot UpdateAttempter::GetRollbackSlot() const { vector<BootControlInterface::Slot> bootable_slots; for (BootControlInterface::Slot slot = 0; slot < num_slots; slot++) { if (slot != current_slot && - system_state_->boot_control()->IsSlotBootable(slot)) { + SystemState::Get()->boot_control()->IsSlotBootable(slot)) { LOG(INFO) << "Found bootable slot " << BootControlInterface::SlotName(slot); return slot; @@ -790,19 +910,16 @@ BootControlInterface::Slot UpdateAttempter::GetRollbackSlot() const { bool UpdateAttempter::CheckForUpdate(const string& app_version, const string& omaha_url, UpdateAttemptFlags flags) { - dlc_module_ids_.clear(); - is_install_ = false; - bool interactive = !(flags & UpdateAttemptFlags::kFlagNonInteractive); - - if (interactive && status_ != UpdateStatus::IDLE) { - // An update check is either in-progress, or an update has completed and the - // system is in UPDATED_NEED_REBOOT. Either way, don't do an interactive - // update at this time - LOG(INFO) << "Refusing to do an interactive update with an update already " - "in progress"; + if (status_ != UpdateStatus::IDLE) { + LOG(INFO) << "Refusing to do an update as there is an " + << (is_install_ ? "install" : "update") + << " already in progress."; return false; } + bool interactive = !(flags & UpdateAttemptFlags::kFlagNonInteractive); + is_install_ = false; + LOG(INFO) << "Forced update check requested."; forced_app_version_.clear(); forced_omaha_url_.clear(); @@ -829,25 +946,33 @@ bool UpdateAttempter::CheckForUpdate(const string& app_version, // of the previously set ones. current_update_attempt_flags_ = flags; // Note: The caching for non-interactive update checks happens in - // OnUpdateScheduled(). + // |OnUpdateScheduled()|. } + // |forced_update_pending_callback_| should always be set, but even in the + // case that it is not, we still return true indicating success because the + // scheduled periodic check will pick up these changes. if (forced_update_pending_callback_.get()) { - if (!system_state_->dlcservice()->GetInstalled(&dlc_module_ids_)) { - dlc_module_ids_.clear(); - } - // Make sure that a scheduling request is made prior to calling the forced - // update pending callback. + // Always call |ScheduleUpdates()| before forcing an update. This is because + // we need an update to be scheduled for the + // |forced_update_pending_callback_| to have an effect. Here we don't need + // to care about the return value from |ScheduleUpdate()|. ScheduleUpdates(); forced_update_pending_callback_->Run(true, interactive); } - return true; } -bool UpdateAttempter::CheckForInstall(const vector<string>& dlc_module_ids, +bool UpdateAttempter::CheckForInstall(const vector<string>& dlc_ids, const string& omaha_url) { - dlc_module_ids_ = dlc_module_ids; + if (status_ != UpdateStatus::IDLE) { + LOG(INFO) << "Refusing to do an install as there is an " + << (is_install_ ? "install" : "update") + << " already in progress."; + return false; + } + + dlc_ids_ = dlc_ids; is_install_ = true; forced_omaha_url_.clear(); @@ -857,35 +982,28 @@ bool UpdateAttempter::CheckForInstall(const vector<string>& dlc_module_ids, if (IsAnyUpdateSourceAllowed()) { forced_omaha_url_ = omaha_url; } - if (omaha_url == kScheduledAUTestURLRequest) { - forced_omaha_url_ = constants::kOmahaDefaultAUTestURL; - } else if (omaha_url == kAUTestURLRequest) { + + if (omaha_url == kScheduledAUTestURLRequest || + omaha_url == kAUTestURLRequest) { forced_omaha_url_ = constants::kOmahaDefaultAUTestURL; } - if (!ScheduleUpdates()) { - if (forced_update_pending_callback_.get()) { - // Make sure that a scheduling request is made prior to calling the forced - // update pending callback. - ScheduleUpdates(); - forced_update_pending_callback_->Run(true, true); - return true; - } - return false; + // |forced_update_pending_callback_| should always be set, but even in the + // case that it is not, we still return true indicating success because the + // scheduled periodic check will pick up these changes. + if (forced_update_pending_callback_.get()) { + // Always call |ScheduleUpdates()| before forcing an update. This is because + // we need an update to be scheduled for the + // |forced_update_pending_callback_| to have an effect. Here we don't need + // to care about the return value from |ScheduleUpdate()|. + ScheduleUpdates(); + forced_update_pending_callback_->Run(true, true); } return true; } bool UpdateAttempter::RebootIfNeeded() { -#ifdef __ANDROID__ - if (status_ != UpdateStatus::UPDATED_NEED_REBOOT) { - LOG(INFO) << "Reboot requested, but status is " - << UpdateStatusToString(status_) << ", so not rebooting."; - return false; - } -#endif // __ANDROID__ - - if (system_state_->power_manager()->RequestReboot()) + if (SystemState::Get()->power_manager()->RequestReboot()) return true; return RebootDirectly(); @@ -897,18 +1015,14 @@ void UpdateAttempter::WriteUpdateCompletedMarker() { return; prefs_->SetString(kPrefsUpdateCompletedOnBootId, boot_id); - int64_t value = system_state_->clock()->GetBootTime().ToInternalValue(); + int64_t value = SystemState::Get()->clock()->GetBootTime().ToInternalValue(); prefs_->SetInt64(kPrefsUpdateCompletedBootTime, value); } bool UpdateAttempter::RebootDirectly() { - vector<string> command; - command.push_back("/sbin/shutdown"); - command.push_back("-r"); - command.push_back("now"); - LOG(INFO) << "Running \"" << base::JoinString(command, " ") << "\""; + vector<string> command = {"/sbin/shutdown", "-r", "now"}; int rc = 0; - Subprocess::SynchronousExec(command, &rc, nullptr); + Subprocess::SynchronousExec(command, &rc, nullptr, nullptr); return rc == 0; } @@ -941,13 +1055,7 @@ void UpdateAttempter::OnUpdateScheduled(EvalStatus status, LOG(INFO) << "Update attempt flags in use = 0x" << std::hex << current_update_attempt_flags_; - Update(forced_app_version_, - forced_omaha_url_, - params.target_channel, - params.target_version_prefix, - params.rollback_allowed, - /*obey_proxies=*/false, - params.interactive); + Update(params); // Always clear the forced app_version and omaha_url after an update attempt // so the next update uses the defaults. forced_app_version_.clear(); @@ -963,45 +1071,37 @@ void UpdateAttempter::OnUpdateScheduled(EvalStatus status, // a bug that will most likely prevent further automatic update checks. It // seems better to crash in such cases and restart the update_engine daemon // into, hopefully, a known good state. - CHECK(IsUpdateRunningOrScheduled()); + CHECK(IsBusyOrUpdateScheduled()); } void UpdateAttempter::UpdateLastCheckedTime() { - last_checked_time_ = system_state_->clock()->GetWallclockTime().ToTimeT(); + last_checked_time_ = + SystemState::Get()->clock()->GetWallclockTime().ToTimeT(); } void UpdateAttempter::UpdateRollbackHappened() { - DCHECK(system_state_); - DCHECK(system_state_->payload_state()); + DCHECK(SystemState::Get()->payload_state()); DCHECK(policy_provider_); - if (system_state_->payload_state()->GetRollbackHappened() && + if (SystemState::Get()->payload_state()->GetRollbackHappened() && (policy_provider_->device_policy_is_loaded() || policy_provider_->IsConsumerDevice())) { // Rollback happened, but we already went through OOBE and policy is // present or it's a consumer device. - system_state_->payload_state()->SetRollbackHappened(false); + SystemState::Get()->payload_state()->SetRollbackHappened(false); } } -// Delegate methods: -void UpdateAttempter::ProcessingDone(const ActionProcessor* processor, - ErrorCode code) { - LOG(INFO) << "Processing Done."; - +void UpdateAttempter::ProcessingDoneInternal(const ActionProcessor* processor, + ErrorCode code) { // Reset cpu shares back to normal. cpu_limiter_.StopLimiter(); - // reset the state that's only valid for a single update pass - current_update_attempt_flags_ = UpdateAttemptFlags::kNone; - - if (forced_update_pending_callback_.get()) - // Clear prior interactive requests once the processor is done. - forced_update_pending_callback_->Run(false, false); + ResetInteractivityFlags(); if (status_ == UpdateStatus::REPORTING_ERROR_EVENT) { LOG(INFO) << "Error event sent."; - // Inform scheduler of new status; + // Inform scheduler of new status. SetStatusAndNotify(UpdateStatus::IDLE); ScheduleUpdates(); @@ -1014,93 +1114,126 @@ void UpdateAttempter::ProcessingDone(const ActionProcessor* processor, attempt_error_code_ = utils::GetBaseErrorCode(code); - if (code == ErrorCode::kSuccess) { - // For install operation, we do not mark update complete since we do not - // need reboot. - if (!is_install_) - WriteUpdateCompletedMarker(); - ReportTimeToUpdateAppliedMetric(); - - prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0); - prefs_->SetString(kPrefsPreviousVersion, - omaha_request_params_->app_version()); - DeltaPerformer::ResetUpdateProgress(prefs_, false); - - system_state_->payload_state()->UpdateSucceeded(); - - // Since we're done with scattering fully at this point, this is the - // safest point delete the state files, as we're sure that the status is - // set to reboot (which means no more updates will be applied until reboot) - // This deletion is required for correctness as we want the next update - // check to re-create a new random number for the update check count. - // Similarly, we also delete the wall-clock-wait period that was persisted - // so that we start with a new random value for the next update check - // after reboot so that the same device is not favored or punished in any - // way. - prefs_->Delete(kPrefsUpdateCheckCount); - system_state_->payload_state()->SetScatteringWaitPeriod(TimeDelta()); - system_state_->payload_state()->SetStagingWaitPeriod(TimeDelta()); - prefs_->Delete(kPrefsUpdateFirstSeenAt); - - if (is_install_) { - LOG(INFO) << "DLC successfully installed, no reboot needed."; - SetStatusAndNotify(UpdateStatus::IDLE); - ScheduleUpdates(); + if (code != ErrorCode::kSuccess) { + if (ScheduleErrorEventAction()) { return; } - - SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT); + LOG(INFO) << "No update."; + SetStatusAndNotify(UpdateStatus::IDLE); ScheduleUpdates(); - LOG(INFO) << "Update successfully applied, waiting to reboot."; - - // |install_plan_| is null during rollback operations, and the stats don't - // make much sense then anyway. - if (install_plan_) { - // Generate an unique payload identifier. - string target_version_uid; - for (const auto& payload : install_plan_->payloads) { - target_version_uid += - brillo::data_encoding::Base64Encode(payload.hash) + ":" + - payload.metadata_signature + ":"; - } - - // If we just downloaded a rollback image, we should preserve this fact - // over the following powerwash. - if (install_plan_->is_rollback) { - system_state_->payload_state()->SetRollbackHappened(true); - system_state_->metrics_reporter()->ReportEnterpriseRollbackMetrics( - /*success=*/true, install_plan_->version); - } - - // Expect to reboot into the new version to send the proper metric during - // next boot. - system_state_->payload_state()->ExpectRebootInNewVersion( - target_version_uid); - } else { - // If we just finished a rollback, then we expect to have no Omaha - // response. Otherwise, it's an error. - if (system_state_->payload_state()->GetRollbackVersion().empty()) { - LOG(ERROR) << "Can't send metrics because there was no Omaha response"; - } - } return; } - if (ScheduleErrorEventAction()) { - return; + ReportTimeToUpdateAppliedMetric(); + prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0); + prefs_->SetString(kPrefsPreviousVersion, + omaha_request_params_->app_version()); + DeltaPerformer::ResetUpdateProgress(prefs_, false); + + SystemState::Get()->payload_state()->UpdateSucceeded(); + + // Since we're done with scattering fully at this point, this is the + // safest point delete the state files, as we're sure that the status is + // set to reboot (which means no more updates will be applied until reboot) + // This deletion is required for correctness as we want the next update + // check to re-create a new random number for the update check count. + // Similarly, we also delete the wall-clock-wait period that was persisted + // so that we start with a new random value for the next update check + // after reboot so that the same device is not favored or punished in any + // way. + prefs_->Delete(kPrefsUpdateCheckCount); + SystemState::Get()->payload_state()->SetScatteringWaitPeriod(TimeDelta()); + SystemState::Get()->payload_state()->SetStagingWaitPeriod(TimeDelta()); + prefs_->Delete(kPrefsUpdateFirstSeenAt); + + // Note: below this comment should only be on |ErrorCode::kSuccess|. + if (is_install_) { + ProcessingDoneInstall(processor, code); + } else { + ProcessingDoneUpdate(processor, code); } - LOG(INFO) << "No update."; +} + +vector<string> UpdateAttempter::GetSuccessfulDlcIds() { + vector<string> dlc_ids; + for (const auto& pr : omaha_request_params_->dlc_apps_params()) + if (pr.second.updated) + dlc_ids.push_back(pr.second.name); + return dlc_ids; +} + +void UpdateAttempter::ProcessingDoneInstall(const ActionProcessor* processor, + ErrorCode code) { + if (!SystemState::Get()->dlcservice()->InstallCompleted( + GetSuccessfulDlcIds())) + LOG(WARNING) << "dlcservice didn't successfully handle install completion."; SetStatusAndNotify(UpdateStatus::IDLE); ScheduleUpdates(); + LOG(INFO) << "DLC successfully installed, no reboot needed."; +} + +void UpdateAttempter::ProcessingDoneUpdate(const ActionProcessor* processor, + ErrorCode code) { + WriteUpdateCompletedMarker(); + + if (!SystemState::Get()->dlcservice()->UpdateCompleted(GetSuccessfulDlcIds())) + LOG(WARNING) << "dlcservice didn't successfully handle update completion."; + SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT); + ScheduleUpdates(); + LOG(INFO) << "Update successfully applied, waiting to reboot."; + + // |install_plan_| is null during rollback operations, and the stats don't + // make much sense then anyway. + if (install_plan_) { + // Generate an unique payload identifier. + string target_version_uid; + for (const auto& payload : install_plan_->payloads) { + target_version_uid += brillo::data_encoding::Base64Encode(payload.hash) + + ":" + payload.metadata_signature + ":"; + // Set fingerprint value for updates only. + if (!is_install_) + SetPref(kPrefsLastFp, payload.fp, payload.app_id); + } + + // If we just downloaded a rollback image, we should preserve this fact + // over the following powerwash. + if (install_plan_->is_rollback) { + SystemState::Get()->payload_state()->SetRollbackHappened(true); + SystemState::Get()->metrics_reporter()->ReportEnterpriseRollbackMetrics( + /*success=*/true, install_plan_->version); + } + + // Expect to reboot into the new version to send the proper metric during + // next boot. + SystemState::Get()->payload_state()->ExpectRebootInNewVersion( + target_version_uid); + } else { + // If we just finished a rollback, then we expect to have no Omaha + // response. Otherwise, it's an error. + if (SystemState::Get()->payload_state()->GetRollbackVersion().empty()) { + LOG(ERROR) << "Can't send metrics because there was no Omaha response"; + } + } +} + +// Delegate methods: +void UpdateAttempter::ProcessingDone(const ActionProcessor* processor, + ErrorCode code) { + LOG(INFO) << "Processing Done."; + ProcessingDoneInternal(processor, code); + + // Note: do cleanups here for any variables that need to be reset after a + // failure, error, update, or install. + is_install_ = false; } void UpdateAttempter::ProcessingStopped(const ActionProcessor* processor) { // Reset cpu shares back to normal. cpu_limiter_.StopLimiter(); download_progress_ = 0.0; - if (forced_update_pending_callback_.get()) - // Clear prior interactive requests once the processor is done. - forced_update_pending_callback_->Run(false, false); + + ResetInteractivityFlags(); + SetStatusAndNotify(UpdateStatus::IDLE); ScheduleUpdates(); error_event_.reset(nullptr); @@ -1116,9 +1249,10 @@ void UpdateAttempter::ActionCompleted(ActionProcessor* processor, // actions (update download as well as the initial update check // actions). const string type = action->Type(); - if (type == DownloadAction::StaticType()) { + if (type == DownloadActionChromeos::StaticType()) { download_progress_ = 0.0; - DownloadAction* download_action = static_cast<DownloadAction*>(action); + DownloadActionChromeos* download_action = + static_cast<DownloadActionChromeos*>(action); http_response_code_ = download_action->GetHTTPResponseCode(); } else if (type == OmahaRequestAction::StaticType()) { OmahaRequestAction* omaha_request_action = @@ -1168,7 +1302,6 @@ void UpdateAttempter::ActionCompleted(ActionProcessor* processor, new InstallPlan(omaha_response_handler_action->install_plan())); UpdateLastCheckedTime(); new_version_ = install_plan_->version; - new_system_version_ = install_plan_->system_version; new_payload_size_ = 0; for (const auto& payload : install_plan_->payloads) new_payload_size_ += payload.size; @@ -1195,6 +1328,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; } @@ -1225,7 +1359,7 @@ void UpdateAttempter::BytesReceived(uint64_t bytes_progressed, uint64_t total) { // The PayloadState keeps track of how many bytes were actually downloaded // from a given URL for the URL skipping logic. - system_state_->payload_state()->DownloadProgress(bytes_progressed); + SystemState::Get()->payload_state()->DownloadProgress(bytes_progressed); double progress = 0; if (total) @@ -1239,7 +1373,7 @@ void UpdateAttempter::BytesReceived(uint64_t bytes_progressed, } void UpdateAttempter::DownloadComplete() { - system_state_->payload_state()->DownloadComplete(); + SystemState::Get()->payload_state()->DownloadComplete(); } void UpdateAttempter::ProgressUpdate(double progress) { @@ -1254,6 +1388,15 @@ void UpdateAttempter::ProgressUpdate(double progress) { } } +void UpdateAttempter::ResetInteractivityFlags() { + // Reset the state that's only valid for a single update pass. + current_update_attempt_flags_ = UpdateAttemptFlags::kNone; + + if (forced_update_pending_callback_.get()) + // Clear prior interactive requests once the processor is done. + forced_update_pending_callback_->Run(false, false); +} + bool UpdateAttempter::ResetStatus() { LOG(INFO) << "Attempting to reset state from " << UpdateStatusToString(status_) << " to UpdateStatus::IDLE"; @@ -1272,9 +1415,10 @@ bool UpdateAttempter::ResetStatus() { // UpdateStatus::UPDATED_NEED_REBOOT state. ret_value = prefs_->Delete(kPrefsUpdateCompletedOnBootId) && ret_value; ret_value = prefs_->Delete(kPrefsUpdateCompletedBootTime) && ret_value; + ret_value = prefs_->Delete(kPrefsLastFp, {kDlcPrefsSubDir}) && ret_value; // Update the boot flags so the current slot has higher priority. - BootControlInterface* boot_control = system_state_->boot_control(); + BootControlInterface* boot_control = SystemState::Get()->boot_control(); if (!boot_control->SetActiveBootSlot(boot_control->GetCurrentSlot())) ret_value = false; @@ -1285,7 +1429,7 @@ bool UpdateAttempter::ResetStatus() { ret_value = false; // Notify the PayloadState that the successful payload was canceled. - system_state_->payload_state()->ResetUpdateStatus(); + SystemState::Get()->payload_state()->ResetUpdateStatus(); // The previous version is used to report back to omaha after reboot that // we actually rebooted into the new version from this "prev-version". We @@ -1307,11 +1451,26 @@ bool UpdateAttempter::GetStatus(UpdateEngineStatus* out_status) { out_status->last_checked_time = last_checked_time_; out_status->status = status_; out_status->current_version = omaha_request_params_->app_version(); - out_status->current_system_version = omaha_request_params_->system_version(); out_status->progress = download_progress_; out_status->new_size_bytes = new_payload_size_; out_status->new_version = new_version_; - out_status->new_system_version = new_system_version_; + out_status->is_enterprise_rollback = + install_plan_ && install_plan_->is_rollback; + out_status->is_install = is_install_; + + string str_eol_date; + if (SystemState::Get()->prefs()->Exists(kPrefsOmahaEolDate) && + !SystemState::Get()->prefs()->GetString(kPrefsOmahaEolDate, + &str_eol_date)) + LOG(ERROR) << "Failed to retrieve kPrefsOmahaEolDate pref."; + out_status->eol_date = StringToEolDate(str_eol_date); + + // A powerwash will take place either if the install plan says it is required + // or if an enterprise rollback is happening. + out_status->will_powerwash_after_reboot = + install_plan_ && + (install_plan_->powerwash_required || install_plan_->is_rollback); + return true; } @@ -1329,13 +1488,13 @@ void UpdateAttempter::BroadcastStatus() { uint32_t UpdateAttempter::GetErrorCodeFlags() { uint32_t flags = 0; - if (!system_state_->hardware()->IsNormalBootMode()) + if (!SystemState::Get()->hardware()->IsNormalBootMode()) flags |= static_cast<uint32_t>(ErrorCode::kDevModeFlag); if (install_plan_ && install_plan_->is_resume) flags |= static_cast<uint32_t>(ErrorCode::kResumedFlag); - if (!system_state_->hardware()->IsOfficialBuild()) + if (!SystemState::Get()->hardware()->IsOfficialBuild()) flags |= static_cast<uint32_t>(ErrorCode::kTestImageFlag); if (!omaha_request_params_->IsUpdateUrlOfficial()) { @@ -1348,7 +1507,7 @@ uint32_t UpdateAttempter::GetErrorCodeFlags() { bool UpdateAttempter::ShouldCancel(ErrorCode* cancel_reason) { // Check if the channel we're attempting to update to is the same as the // target channel currently chosen by the user. - OmahaRequestParams* params = system_state_->request_params(); + OmahaRequestParams* params = SystemState::Get()->request_params(); if (params->download_channel() != params->target_channel()) { LOG(ERROR) << "Aborting download as target channel: " << params->target_channel() @@ -1406,22 +1565,22 @@ bool UpdateAttempter::ScheduleErrorEventAction() { return false; LOG(ERROR) << "Update failed."; - system_state_->payload_state()->UpdateFailed(error_event_->error_code); + SystemState::Get()->payload_state()->UpdateFailed(error_event_->error_code); // Send metrics if it was a rollback. if (install_plan_ && install_plan_->is_rollback) { - system_state_->metrics_reporter()->ReportEnterpriseRollbackMetrics( + SystemState::Get()->metrics_reporter()->ReportEnterpriseRollbackMetrics( /*success=*/false, install_plan_->version); } // Send it to Omaha. LOG(INFO) << "Reporting the error event"; auto error_event_action = std::make_unique<OmahaRequestAction>( - system_state_, error_event_.release(), // Pass ownership. std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(), - system_state_->hardware()), - false); + SystemState::Get()->hardware()), + false, + session_id_); processor_->EnqueueAction(std::move(error_event_action)); SetStatusAndNotify(UpdateStatus::REPORTING_ERROR_EVENT); processor_->StartProcessing(); @@ -1459,12 +1618,14 @@ void UpdateAttempter::MarkDeltaUpdateFailure() { void UpdateAttempter::PingOmaha() { if (!processor_->IsRunning()) { + ResetInteractivityFlags(); + auto ping_action = std::make_unique<OmahaRequestAction>( - system_state_, nullptr, std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(), - system_state_->hardware()), - true); + SystemState::Get()->hardware()), + true, + "" /* session_id */); processor_->set_delegate(nullptr); processor_->EnqueueAction(std::move(ping_action)); // Call StartProcessing() synchronously here to avoid any race conditions @@ -1545,9 +1706,9 @@ void UpdateAttempter::UpdateEngineStarted() { // in case we rebooted because of a crash of the old version, so we // can do a proper crash report with correct information. // This must be done before calling - // system_state_->payload_state()->UpdateEngineStarted() since it will + // SystemState::Get()->payload_state()->UpdateEngineStarted() since it will // delete SystemUpdated marker file. - if (system_state_->system_rebooted() && + if (SystemState::Get()->system_rebooted() && prefs_->Exists(kPrefsSystemUpdatedMarker)) { if (!prefs_->GetString(kPrefsPreviousVersion, &prev_version_)) { // If we fail to get the version string, make sure it stays empty. @@ -1555,18 +1716,38 @@ void UpdateAttempter::UpdateEngineStarted() { } } - system_state_->payload_state()->UpdateEngineStarted(); + MoveToPrefs({kPrefsLastRollCallPingDay, kPrefsLastActivePingDay}); + + SystemState::Get()->payload_state()->UpdateEngineStarted(); StartP2PAtStartup(); + + excluder_ = CreateExcluder(); +} + +void UpdateAttempter::MoveToPrefs(const vector<string>& keys) { + auto* powerwash_safe_prefs = SystemState::Get()->powerwash_safe_prefs(); + for (const auto& key : keys) { + // Do not overwrite existing pref key with powerwash prefs. + if (!prefs_->Exists(key) && powerwash_safe_prefs->Exists(key)) { + string value; + if (!powerwash_safe_prefs->GetString(key, &value) || + !prefs_->SetString(key, value)) { + PLOG(ERROR) << "Unable to add powerwash safe key " << key + << " to prefs. Powerwash safe key will be deleted."; + } + } + // Delete keys regardless of operation success to preserve privacy. + powerwash_safe_prefs->Delete(key); + } } bool UpdateAttempter::StartP2PAtStartup() { - if (system_state_ == nullptr || - !system_state_->p2p_manager()->IsP2PEnabled()) { + if (!SystemState::Get()->p2p_manager()->IsP2PEnabled()) { LOG(INFO) << "Not starting p2p at startup since it's not enabled."; return false; } - if (system_state_->p2p_manager()->CountSharedFiles() < 1) { + if (SystemState::Get()->p2p_manager()->CountSharedFiles() < 1) { LOG(INFO) << "Not starting p2p at startup since our application " << "is not sharing any files."; return false; @@ -1576,22 +1757,19 @@ bool UpdateAttempter::StartP2PAtStartup() { } bool UpdateAttempter::StartP2PAndPerformHousekeeping() { - if (system_state_ == nullptr) - return false; - - if (!system_state_->p2p_manager()->IsP2PEnabled()) { + if (!SystemState::Get()->p2p_manager()->IsP2PEnabled()) { LOG(INFO) << "Not starting p2p since it's not enabled."; return false; } LOG(INFO) << "Ensuring that p2p is running."; - if (!system_state_->p2p_manager()->EnsureP2PRunning()) { + if (!SystemState::Get()->p2p_manager()->EnsureP2PRunning()) { LOG(ERROR) << "Error starting p2p."; return false; } LOG(INFO) << "Performing p2p housekeeping."; - if (!system_state_->p2p_manager()->PerformHousekeeping()) { + if (!SystemState::Get()->p2p_manager()->PerformHousekeeping()) { LOG(ERROR) << "Error performing housekeeping for p2p."; return false; } @@ -1626,7 +1804,7 @@ bool UpdateAttempter::GetBootTimeAtUpdate(Time* out_boot_time) { return true; } -bool UpdateAttempter::IsUpdateRunningOrScheduled() { +bool UpdateAttempter::IsBusyOrUpdateScheduled() { return ((status_ != UpdateStatus::IDLE && status_ != UpdateStatus::UPDATED_NEED_REBOOT) || waiting_for_scheduled_check_); @@ -1638,12 +1816,12 @@ bool UpdateAttempter::IsAnyUpdateSourceAllowed() const { // * The debugd dev features are accessible (i.e. in devmode with no owner). // This protects users running a base image, while still allowing a specific // window (gated by the debug dev features) where `cros flash` is usable. - if (!system_state_->hardware()->IsOfficialBuild()) { + if (!SystemState::Get()->hardware()->IsOfficialBuild()) { LOG(INFO) << "Non-official build; allowing any update source."; return true; } - if (system_state_->hardware()->AreDevFeaturesEnabled()) { + if (SystemState::Get()->hardware()->AreDevFeaturesEnabled()) { LOG(INFO) << "Developer features enabled; allowing custom update sources."; return true; } @@ -1654,20 +1832,22 @@ bool UpdateAttempter::IsAnyUpdateSourceAllowed() const { } void UpdateAttempter::ReportTimeToUpdateAppliedMetric() { - const policy::DevicePolicy* device_policy = system_state_->device_policy(); + const policy::DevicePolicy* device_policy = + SystemState::Get()->device_policy(); if (device_policy && device_policy->IsEnterpriseEnrolled()) { vector<policy::DevicePolicy::WeeklyTimeInterval> parsed_intervals; bool has_time_restrictions = device_policy->GetDisallowedTimeIntervals(&parsed_intervals); int64_t update_first_seen_at_int; - if (system_state_->prefs()->Exists(kPrefsUpdateFirstSeenAt)) { - if (system_state_->prefs()->GetInt64(kPrefsUpdateFirstSeenAt, - &update_first_seen_at_int)) { + if (SystemState::Get()->prefs()->Exists(kPrefsUpdateFirstSeenAt)) { + if (SystemState::Get()->prefs()->GetInt64(kPrefsUpdateFirstSeenAt, + &update_first_seen_at_int)) { TimeDelta update_delay = - system_state_->clock()->GetWallclockTime() - + SystemState::Get()->clock()->GetWallclockTime() - Time::FromInternalValue(update_first_seen_at_int); - system_state_->metrics_reporter() + SystemState::Get() + ->metrics_reporter() ->ReportEnterpriseUpdateSeenToDownloadDays(has_time_restrictions, update_delay.InDays()); } diff --git a/update_attempter.h b/cros/update_attempter.h index c27f8a40..6010484a 100644 --- a/update_attempter.h +++ b/cros/update_attempter.h @@ -14,8 +14,8 @@ // limitations under the License. // -#ifndef UPDATE_ENGINE_UPDATE_ATTEMPTER_H_ -#define UPDATE_ENGINE_UPDATE_ATTEMPTER_H_ +#ifndef UPDATE_ENGINE_CROS_UPDATE_ATTEMPTER_H_ +#define UPDATE_ENGINE_CROS_UPDATE_ATTEMPTER_H_ #include <time.h> @@ -26,23 +26,25 @@ #include <vector> #include <base/bind.h> +#include <base/guid.h> #include <base/time/time.h> #include <gtest/gtest_prod.h> // for FRIEND_TEST -#if USE_CHROME_NETWORK_PROXY -#include "update_engine/chrome_browser_proxy_resolver.h" -#endif // USE_CHROME_NETWORK_PROXY #include "update_engine/certificate_checker.h" #include "update_engine/client_library/include/update_engine/update_status.h" #include "update_engine/common/action_processor.h" #include "update_engine/common/cpu_limiter.h" +#include "update_engine/common/daemon_state_interface.h" +#include "update_engine/common/download_action.h" +#include "update_engine/common/excluder_interface.h" #include "update_engine/common/proxy_resolver.h" -#include "update_engine/omaha_request_params.h" -#include "update_engine/omaha_response_handler_action.h" -#include "update_engine/payload_consumer/download_action.h" +#include "update_engine/common/service_observer_interface.h" +#include "update_engine/common/system_state.h" +#include "update_engine/cros/chrome_browser_proxy_resolver.h" +#include "update_engine/cros/omaha_request_builder_xml.h" +#include "update_engine/cros/omaha_request_params.h" +#include "update_engine/cros/omaha_response_handler_action.h" #include "update_engine/payload_consumer/postinstall_runner_action.h" -#include "update_engine/service_observer_interface.h" -#include "update_engine/system_state.h" #include "update_engine/update_manager/policy.h" #include "update_engine/update_manager/staging_utils.h" #include "update_engine/update_manager/update_manager.h" @@ -56,13 +58,14 @@ namespace chromeos_update_engine { class UpdateAttempter : public ActionProcessorDelegate, public DownloadActionDelegate, public CertificateChecker::Observer, - public PostinstallRunnerAction::DelegateInterface { + public PostinstallRunnerAction::DelegateInterface, + public DaemonStateInterface { public: using UpdateStatus = update_engine::UpdateStatus; using UpdateAttemptFlags = update_engine::UpdateAttemptFlags; static const int kMaxDeltaUpdateFailures; - UpdateAttempter(SystemState* system_state, CertificateChecker* cert_checker); + explicit UpdateAttempter(CertificateChecker* cert_checker); ~UpdateAttempter() override; // Further initialization to be done post construction. @@ -73,19 +76,8 @@ class UpdateAttempter : public ActionProcessorDelegate, virtual bool ScheduleUpdates(); // Checks for update and, if a newer version is available, attempts to update - // the system. Non-empty |in_app_version| or |in_update_url| prevents - // automatic detection of the parameter. |target_channel| denotes a - // policy-mandated channel we are updating to, if not empty. If |obey_proxies| - // is true, the update will likely respect Chrome's proxy setting. For - // security reasons, we may still not honor them. |interactive| should be true - // if this was called from the user (ie dbus). - virtual void Update(const std::string& app_version, - const std::string& omaha_url, - const std::string& target_channel, - const std::string& target_version_prefix, - bool rollback_allowed, - bool obey_proxies, - bool interactive); + // the system. + virtual void Update(const chromeos_update_manager::UpdateCheckParams& params); // ActionProcessorDelegate methods: void ProcessingDone(const ActionProcessor* processor, @@ -137,7 +129,7 @@ class UpdateAttempter : public ActionProcessorDelegate, UpdateAttemptFlags flags); // This is the version of CheckForUpdate called by AttemptInstall API. - virtual bool CheckForInstall(const std::vector<std::string>& dlc_module_ids, + virtual bool CheckForInstall(const std::vector<std::string>& dlc_ids, const std::string& omaha_url); // This is the internal entry point for going through a rollback. This will @@ -158,6 +150,9 @@ class UpdateAttempter : public ActionProcessorDelegate, // UPDATED_NEED_REBOOT. Returns true on success, false otherwise. bool RebootIfNeeded(); + // Sets the DLC as active or inactive. See chromeos/common_service.h + virtual bool SetDlcActiveValue(bool is_active, const std::string& dlc_id); + // DownloadActionDelegate methods: void BytesReceived(uint64_t bytes_progressed, uint64_t bytes_received, @@ -177,6 +172,9 @@ class UpdateAttempter : public ActionProcessorDelegate, // Called at update_engine startup to do various house-keeping. void UpdateEngineStarted(); + // Returns the |Excluder| that is currently held onto. + virtual ExcluderInterface* GetExcluder() const { return excluder_.get(); } + // Reloads the device policy from libbrillo. Note: This method doesn't // cause a real-time policy fetch from the policy server. It just reloads the // latest value that libbrillo has cached. libbrillo fetches the policies @@ -221,15 +219,15 @@ class UpdateAttempter : public ActionProcessorDelegate, // 'cros flash' to function properly). bool IsAnyUpdateSourceAllowed() const; - // Add and remove a service observer. - void AddObserver(ServiceObserverInterface* observer) { + // |DaemonStateInterface| overrides. + bool StartUpdater() override; + void AddObserver(ServiceObserverInterface* observer) override { service_observers_.insert(observer); } - void RemoveObserver(ServiceObserverInterface* observer) { + void RemoveObserver(ServiceObserverInterface* observer) override { service_observers_.erase(observer); } - - const std::set<ServiceObserverInterface*>& service_observers() { + const std::set<ServiceObserverInterface*>& service_observers() override { return service_observers_; } @@ -245,38 +243,64 @@ class UpdateAttempter : public ActionProcessorDelegate, FRIEND_TEST(UpdateAttempterTest, ActionCompletedOmahaRequestTest); FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile); FRIEND_TEST(UpdateAttempterTest, BroadcastCompleteDownloadTest); + FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsInstallTest); + FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsNoPrefFilesTest); + FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsNonParseableValuesTest); + FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsValidValuesTest); + FRIEND_TEST(UpdateAttempterTest, CalculateDlcParamsRemoveStaleMetadata); FRIEND_TEST(UpdateAttempterTest, ChangeToDownloadingOnReceivedBytesTest); + FRIEND_TEST(UpdateAttempterTest, CheckForInstallNotIdleFails); FRIEND_TEST(UpdateAttempterTest, CheckForUpdateAUDlcTest); FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventTest); FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest); FRIEND_TEST(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest); FRIEND_TEST(UpdateAttempterTest, DownloadProgressAccumulationTest); FRIEND_TEST(UpdateAttempterTest, InstallSetsStatusIdle); + FRIEND_TEST(UpdateAttempterTest, IsEnterpriseRollbackInGetStatusTrue); + FRIEND_TEST(UpdateAttempterTest, IsEnterpriseRollbackInGetStatusFalse); + FRIEND_TEST(UpdateAttempterTest, + PowerwashInGetStatusTrueBecausePowerwashRequired); + FRIEND_TEST(UpdateAttempterTest, PowerwashInGetStatusTrueBecauseRollback); FRIEND_TEST(UpdateAttempterTest, MarkDeltaUpdateFailureTest); FRIEND_TEST(UpdateAttempterTest, PingOmahaTest); + FRIEND_TEST(UpdateAttempterTest, ProcessingDoneInstallError); + FRIEND_TEST(UpdateAttempterTest, ProcessingDoneUpdateError); FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics); FRIEND_TEST(UpdateAttempterTest, RollbackNotAllowed); FRIEND_TEST(UpdateAttempterTest, RollbackAfterInstall); FRIEND_TEST(UpdateAttempterTest, RollbackAllowed); FRIEND_TEST(UpdateAttempterTest, RollbackAllowedSetAndReset); + FRIEND_TEST(UpdateAttempterTest, ChannelDowngradeNoRollback); + FRIEND_TEST(UpdateAttempterTest, ChannelDowngradeRollback); FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackFailure); FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackSuccess); FRIEND_TEST(UpdateAttempterTest, RollbackMetricsRollbackFailure); FRIEND_TEST(UpdateAttempterTest, RollbackMetricsRollbackSuccess); FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest); FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionTest); + FRIEND_TEST(UpdateAttempterTest, SessionIdTestEnforceEmptyStrPingOmaha); + FRIEND_TEST(UpdateAttempterTest, SessionIdTestOnOmahaRequestActions); FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedNotRollback); FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedRollback); + FRIEND_TEST(UpdateAttempterTest, TargetChannelHintSetAndReset); FRIEND_TEST(UpdateAttempterTest, TargetVersionPrefixSetAndReset); FRIEND_TEST(UpdateAttempterTest, UpdateAfterInstall); FRIEND_TEST(UpdateAttempterTest, UpdateAttemptFlagsCachedAtUpdateStart); FRIEND_TEST(UpdateAttempterTest, UpdateDeferredByPolicyTest); FRIEND_TEST(UpdateAttempterTest, UpdateIsNotRunningWhenUpdateAvailable); + FRIEND_TEST(UpdateAttempterTest, GetSuccessfulDlcIds); + FRIEND_TEST(UpdateAttempterTest, QuickFixTokenWhenDeviceIsEnterpriseEnrolled); + FRIEND_TEST(UpdateAttempterTest, MoveToPrefs); // Returns the special flags to be added to ErrorCode values based on the // parameters used in the current update attempt. uint32_t GetErrorCodeFlags(); + // ActionProcessorDelegate methods |ProcessingDone()| internal helpers. + void ProcessingDoneInternal(const ActionProcessor* processor, ErrorCode code); + void ProcessingDoneUpdate(const ActionProcessor* processor, ErrorCode code); + void ProcessingDoneInstall(const ActionProcessor* processor, ErrorCode code); + // CertificateChecker::Observer method. // Report metrics about the certificate being checked. void CertificateChecked(ServerToCheck server_to_check, @@ -317,10 +341,8 @@ class UpdateAttempter : public ActionProcessorDelegate, void MarkDeltaUpdateFailure(); ProxyResolver* GetProxyResolver() { -#if USE_CHROME_NETWORK_PROXY if (obeying_proxies_) return &chrome_proxy_resolver_; -#endif // USE_CHROME_NETWORK_PROXY return &direct_proxy_resolver_; } @@ -334,18 +356,13 @@ class UpdateAttempter : public ActionProcessorDelegate, // Helper method of Update() to calculate the update-related parameters // from various sources and set the appropriate state. Please refer to // Update() method for the meaning of the parameters. - bool CalculateUpdateParams(const std::string& app_version, - const std::string& omaha_url, - const std::string& target_channel, - const std::string& target_version_prefix, - bool rollback_allowed, - bool obey_proxies, - bool interactive); + bool CalculateUpdateParams( + const chromeos_update_manager::UpdateCheckParams& params); // Calculates all the scattering related parameters (such as waiting period, // which type of scattering is enabled, etc.) and also updates/deletes // the corresponding prefs file used in scattering. Should be called - // only after the device policy has been loaded and set in the system_state_. + // only after the device policy has been loaded and set in the system state. void CalculateScatteringParams(bool interactive); // Sets a random value for the waiting period to wait for before downloading @@ -371,6 +388,10 @@ class UpdateAttempter : public ActionProcessorDelegate, // on the |omaha_request_params_| object. void CalculateP2PParams(bool interactive); + // For each key, reads value from powerwash safe prefs and adds it to prefs + // if key doesnt already exist. Then deletes the powerwash safe keys. + void MoveToPrefs(const std::vector<std::string>& keys); + // Starts P2P if it's enabled and there are files to actually share. // Called only at program startup. Returns true only if p2p was // started and housekeeping was performed. @@ -398,8 +419,8 @@ class UpdateAttempter : public ActionProcessorDelegate, // policy is available again. void UpdateRollbackHappened(); - // Returns whether an update is currently running or scheduled. - bool IsUpdateRunningOrScheduled(); + // Returns if an update is: running, applied and needs reboot, or scheduled. + bool IsBusyOrUpdateScheduled(); void CalculateStagingParams(bool interactive); @@ -408,6 +429,32 @@ class UpdateAttempter : public ActionProcessorDelegate, // will only be reported for enterprise enrolled devices. void ReportTimeToUpdateAppliedMetric(); + // Resets interactivity and forced update flags. + void ResetInteractivityFlags(); + + // Resets all the DLC prefs. + bool ResetDlcPrefs(const std::string& dlc_id); + + // Sets given pref key for DLC and platform. + void SetPref(const std::string& pref_key, + const std::string& pref_value, + const std::string& payload_id); + + // Get the integer values from the DLC metadata for |kPrefsPingLastActive| + // or |kPrefsPingLastRollcall|. + // The value is equal to -2 when the value cannot be read or is not numeric. + // The value is equal to -1 the first time it is being sent, which is + // when the metadata file doesn't exist. + int64_t GetPingMetadata(const std::string& metadata_key) const; + + // Calculates the update parameters for DLCs. Sets the |dlc_ids_| + // parameter on the |omaha_request_params_| object. + void CalculateDlcParams(); + + // Returns the list of DLC IDs that were installed/updated, excluding the ones + // which had "noupdate" in the Omaha response. + std::vector<std::string> GetSuccessfulDlcIds(); + // Last status notification timestamp used for throttling. Use monotonic // TimeTicks to ensure that notifications are sent even if the system clock is // set back in the middle of an update. @@ -415,15 +462,11 @@ class UpdateAttempter : public ActionProcessorDelegate, // Our two proxy resolvers DirectProxyResolver direct_proxy_resolver_; -#if USE_CHROME_NETWORK_PROXY ChromeBrowserProxyResolver chrome_proxy_resolver_; -#endif // USE_CHROME_NETWORK_PROXY std::unique_ptr<ActionProcessor> processor_; - // External state of the system outside the update_engine process - // carved out separately to mock out easily in unit tests. - SystemState* system_state_; + ActionProcessor aux_processor_; // Pointer to the certificate checker instance to use. CertificateChecker* cert_checker_; @@ -435,7 +478,7 @@ class UpdateAttempter : public ActionProcessorDelegate, std::unique_ptr<InstallPlan> install_plan_; // Pointer to the preferences store interface. This is just a cached - // copy of system_state->prefs() because it's used in many methods and + // copy of SystemState::Get()->prefs() because it's used in many methods and // is convenient this way. PrefsInterface* prefs_ = nullptr; @@ -460,7 +503,6 @@ class UpdateAttempter : public ActionProcessorDelegate, int64_t last_checked_time_ = 0; std::string prev_version_; std::string new_version_ = "0.0.0.0"; - std::string new_system_version_; uint64_t new_payload_size_ = 0; // Flags influencing all periodic update checks UpdateAttemptFlags update_attempt_flags_ = UpdateAttemptFlags::kNone; @@ -509,7 +551,7 @@ class UpdateAttempter : public ActionProcessorDelegate, std::string forced_omaha_url_; // A list of DLC module IDs. - std::vector<std::string> dlc_module_ids_; + std::vector<std::string> dlc_ids_; // Whether the operation is install (write to the current slot not the // inactive slot). bool is_install_; @@ -518,6 +560,12 @@ class UpdateAttempter : public ActionProcessorDelegate, base::TimeDelta staging_wait_time_; chromeos_update_manager::StagingSchedule staging_schedule_; + // This is the session ID used to track update flow to Omaha. + std::string session_id_; + + // Interface for excluder. + std::unique_ptr<ExcluderInterface> excluder_; + DISALLOW_COPY_AND_ASSIGN(UpdateAttempter); }; @@ -530,4 +578,4 @@ ErrorCode GetErrorCodeForAction(AbstractAction* action, ErrorCode code); } // namespace chromeos_update_engine -#endif // UPDATE_ENGINE_UPDATE_ATTEMPTER_H_ +#endif // UPDATE_ENGINE_CROS_UPDATE_ATTEMPTER_H_ diff --git a/cros/update_attempter_unittest.cc b/cros/update_attempter_unittest.cc new file mode 100644 index 00000000..a7f5236e --- /dev/null +++ b/cros/update_attempter_unittest.cc @@ -0,0 +1,2479 @@ +// +// Copyright (C) 2012 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/cros/update_attempter.h" + +#include <stdint.h> + +#include <limits> +#include <map> +#include <memory> +#include <string> +#include <unordered_set> + +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <base/task/single_thread_task_executor.h> +#include <brillo/message_loops/base_message_loop.h> +#include <brillo/message_loops/message_loop.h> +#include <brillo/message_loops/message_loop_utils.h> +#include <gtest/gtest.h> +#include <policy/libpolicy.h> +#include <policy/mock_device_policy.h> +#include <policy/mock_libpolicy.h> + +#include "update_engine/common/constants.h" +#include "update_engine/common/dlcservice_interface.h" +#include "update_engine/common/mock_action.h" +#include "update_engine/common/mock_action_processor.h" +#include "update_engine/common/mock_http_fetcher.h" +#include "update_engine/common/mock_service_observer.h" +#include "update_engine/common/platform_constants.h" +#include "update_engine/common/prefs.h" +#include "update_engine/common/test_utils.h" +#include "update_engine/common/utils.h" +#include "update_engine/cros/download_action_chromeos.h" +#include "update_engine/cros/fake_system_state.h" +#include "update_engine/cros/mock_p2p_manager.h" +#include "update_engine/cros/mock_payload_state.h" +#include "update_engine/cros/omaha_utils.h" +#include "update_engine/libcurl_http_fetcher.h" +#include "update_engine/payload_consumer/filesystem_verifier_action.h" +#include "update_engine/payload_consumer/install_plan.h" +#include "update_engine/payload_consumer/payload_constants.h" +#include "update_engine/payload_consumer/postinstall_runner_action.h" +#include "update_engine/update_boot_flags_action.h" +#include "update_engine/update_manager/mock_update_manager.h" + +using base::Time; +using base::TimeDelta; +using chromeos_update_manager::EvalStatus; +using chromeos_update_manager::MockUpdateManager; +using chromeos_update_manager::StagingSchedule; +using chromeos_update_manager::UpdateCheckParams; +using policy::DevicePolicy; +using std::map; +using std::string; +using std::unique_ptr; +using std::unordered_set; +using std::vector; +using testing::_; +using testing::Contains; +using testing::DoAll; +using testing::ElementsAre; +using testing::Field; +using testing::InSequence; +using testing::Invoke; +using testing::Ne; +using testing::NiceMock; +using testing::Pointee; +using testing::Property; +using testing::Return; +using testing::ReturnPointee; +using testing::ReturnRef; +using testing::SaveArg; +using testing::SetArgPointee; +using update_engine::UpdateAttemptFlags; +using update_engine::UpdateEngineStatus; +using update_engine::UpdateStatus; + +namespace chromeos_update_engine { + +namespace { + +const UpdateStatus kNonIdleUpdateStatuses[] = { + UpdateStatus::CHECKING_FOR_UPDATE, + UpdateStatus::UPDATE_AVAILABLE, + UpdateStatus::DOWNLOADING, + UpdateStatus::VERIFYING, + UpdateStatus::FINALIZING, + UpdateStatus::UPDATED_NEED_REBOOT, + UpdateStatus::REPORTING_ERROR_EVENT, + UpdateStatus::ATTEMPTING_ROLLBACK, + UpdateStatus::DISABLED, + UpdateStatus::NEED_PERMISSION_TO_UPDATE, +}; + +struct CheckForUpdateTestParams { + // Setups + Inputs: + UpdateStatus status = UpdateStatus::IDLE; + string app_version = "fake_app_version"; + string omaha_url = "fake_omaha_url"; + UpdateAttemptFlags flags = UpdateAttemptFlags::kNone; + bool is_official_build = true; + bool are_dev_features_enabled = false; + + // Expects: + string expected_forced_app_version = ""; + string expected_forced_omaha_url = ""; + bool should_schedule_updates_be_called = true; + bool expected_result = true; +}; + +struct OnUpdateScheduledTestParams { + // Setups + Inputs: + UpdateCheckParams params = {}; + EvalStatus status = EvalStatus::kFailed; + // Expects: + UpdateStatus exit_status = UpdateStatus::IDLE; + bool should_schedule_updates_be_called = false; + bool should_update_be_called = false; +}; + +struct ProcessingDoneTestParams { + // Setups + Inputs: + bool is_install = false; + UpdateStatus status = UpdateStatus::CHECKING_FOR_UPDATE; + ActionProcessor* processor = nullptr; + ErrorCode code = ErrorCode::kSuccess; + map<string, OmahaRequestParams::AppParams> dlc_apps_params; + + // Expects: + const bool kExpectedIsInstall = false; + bool should_schedule_updates_be_called = true; + UpdateStatus expected_exit_status = UpdateStatus::IDLE; + bool should_install_completed_be_called = false; + bool should_update_completed_be_called = false; + vector<string> args_to_install_completed; + vector<string> args_to_update_completed; +}; + +class MockDlcService : public DlcServiceInterface { + public: + MOCK_METHOD1(GetDlcsToUpdate, bool(vector<string>*)); + MOCK_METHOD1(InstallCompleted, bool(const vector<string>&)); + MOCK_METHOD1(UpdateCompleted, bool(const vector<string>&)); +}; + +} // namespace + +const char kRollbackVersion[] = "10575.39.2"; + +// Test a subclass rather than the main class directly so that we can mock out +// methods within the class. There're explicit unit tests for the mocked out +// methods. +class UpdateAttempterUnderTest : public UpdateAttempter { + public: + UpdateAttempterUnderTest() : UpdateAttempter(nullptr) {} + + void Update(const UpdateCheckParams& params) override { + update_called_ = true; + if (do_update_) { + UpdateAttempter::Update(params); + return; + } + LOG(INFO) << "[TEST] Update() disabled."; + status_ = UpdateStatus::CHECKING_FOR_UPDATE; + } + + void DisableUpdate() { do_update_ = false; } + + bool WasUpdateCalled() const { return update_called_; } + + // Wrap the update scheduling method, allowing us to opt out of scheduled + // updates for testing purposes. + bool ScheduleUpdates() override { + schedule_updates_called_ = true; + if (do_schedule_updates_) + return UpdateAttempter::ScheduleUpdates(); + LOG(INFO) << "[TEST] Update scheduling disabled."; + waiting_for_scheduled_check_ = true; + return true; + } + + void DisableScheduleUpdates() { do_schedule_updates_ = false; } + + // Indicates whether |ScheduleUpdates()| was called. + bool WasScheduleUpdatesCalled() const { return schedule_updates_called_; } + + // Need to expose following private members of |UpdateAttempter| for tests. + const string& forced_app_version() const { return forced_app_version_; } + const string& forced_omaha_url() const { return forced_omaha_url_; } + + // Need to expose |waiting_for_scheduled_check_| for testing. + void SetWaitingForScheduledCheck(bool waiting) { + waiting_for_scheduled_check_ = waiting; + } + + private: + // Used for overrides of |Update()|. + bool update_called_ = false; + bool do_update_ = true; + + // Used for overrides of |ScheduleUpdates()|. + bool schedule_updates_called_ = false; + bool do_schedule_updates_ = true; +}; + +class UpdateAttempterTest : public ::testing::Test { + protected: + void SetUp() override { + // Override system state members. + FakeSystemState::CreateInstance(); + FakeSystemState::Get()->set_connection_manager(&mock_connection_manager); + FakeSystemState::Get()->set_update_attempter(&attempter_); + FakeSystemState::Get()->set_dlcservice(&mock_dlcservice_); + FakeSystemState::Get()->set_update_manager(&mock_update_manager_); + loop_.SetAsCurrent(); + + prefs_ = FakeSystemState::Get()->fake_prefs(); + certificate_checker_.reset( + new CertificateChecker(prefs_, &openssl_wrapper_)); + certificate_checker_->Init(); + + attempter_.set_forced_update_pending_callback( + new base::Callback<void(bool, bool)>(base::Bind([](bool, bool) {}))); + // Finish initializing the attempter. + attempter_.Init(); + + EXPECT_EQ(0, attempter_.http_response_code_); + EXPECT_EQ(UpdateStatus::IDLE, attempter_.status_); + EXPECT_EQ(0.0, attempter_.download_progress_); + EXPECT_EQ(0, attempter_.last_checked_time_); + EXPECT_EQ("0.0.0.0", attempter_.new_version_); + EXPECT_EQ(0ULL, attempter_.new_payload_size_); + processor_ = new NiceMock<MockActionProcessor>(); + attempter_.processor_.reset(processor_); // Transfers ownership. + + // Setup store/load semantics of P2P properties via the mock |PayloadState|. + actual_using_p2p_for_downloading_ = false; + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), + SetUsingP2PForDownloading(_)) + .WillRepeatedly(SaveArg<0>(&actual_using_p2p_for_downloading_)); + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), + GetUsingP2PForDownloading()) + .WillRepeatedly(ReturnPointee(&actual_using_p2p_for_downloading_)); + actual_using_p2p_for_sharing_ = false; + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), + SetUsingP2PForSharing(_)) + .WillRepeatedly(SaveArg<0>(&actual_using_p2p_for_sharing_)); + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), + GetUsingP2PForDownloading()) + .WillRepeatedly(ReturnPointee(&actual_using_p2p_for_sharing_)); + } + + public: + void ScheduleQuitMainLoop(); + + // Callbacks to run the different tests from the main loop. + void UpdateTestStart(); + void UpdateTestVerify(); + void RollbackTestStart(bool enterprise_rollback, bool valid_slot); + void RollbackTestVerify(); + void PingOmahaTestStart(); + void ReadScatterFactorFromPolicyTestStart(); + void DecrementUpdateCheckCountTestStart(); + void NoScatteringDoneDuringManualUpdateTestStart(); + void P2PNotEnabledStart(); + void P2PEnabledStart(); + void P2PEnabledInteractiveStart(); + void P2PEnabledStartingFailsStart(); + void P2PEnabledHousekeepingFailsStart(); + void SessionIdTestChange(); + void SessionIdTestEnforceEmptyStrPingOmaha(); + void SessionIdTestConsistencyInUpdateFlow(); + void SessionIdTestInDownloadAction(); + void ResetRollbackHappenedStart(bool is_consumer, + bool is_policy_available, + bool expected_reset); + // Staging related callbacks. + void SetUpStagingTest(const StagingSchedule& schedule); + void CheckStagingOff(); + void StagingSetsPrefsAndTurnsOffScatteringStart(); + void StagingOffIfInteractiveStart(); + void StagingOffIfOobeStart(); + + bool actual_using_p2p_for_downloading() { + return actual_using_p2p_for_downloading_; + } + bool actual_using_p2p_for_sharing() { return actual_using_p2p_for_sharing_; } + + // |CheckForUpdate()| related member functions. + void TestCheckForUpdate(); + + // |OnUpdateScheduled()| related member functions. + void TestOnUpdateScheduled(); + + // |ProcessingDone()| related member functions. + void TestProcessingDone(); + + base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO}; + brillo::BaseMessageLoop loop_{base_loop_.task_runner()}; + + UpdateAttempterUnderTest attempter_; + OpenSSLWrapper openssl_wrapper_; + std::unique_ptr<CertificateChecker> certificate_checker_; + MockDlcService mock_dlcservice_; + MockUpdateManager mock_update_manager_; + + NiceMock<MockActionProcessor>* processor_; + NiceMock<MockConnectionManager> mock_connection_manager; + + FakePrefs* prefs_; + + // |CheckForUpdate()| test params. + CheckForUpdateTestParams cfu_params_; + + // |OnUpdateScheduled()| test params. + OnUpdateScheduledTestParams ous_params_; + + // |ProcessingDone()| test params. + ProcessingDoneTestParams pd_params_; + + bool actual_using_p2p_for_downloading_; + bool actual_using_p2p_for_sharing_; +}; + +void UpdateAttempterTest::TestCheckForUpdate() { + // Setup + attempter_.status_ = cfu_params_.status; + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild( + cfu_params_.is_official_build); + FakeSystemState::Get()->fake_hardware()->SetAreDevFeaturesEnabled( + cfu_params_.are_dev_features_enabled); + + // Invocation + EXPECT_EQ( + cfu_params_.expected_result, + attempter_.CheckForUpdate( + cfu_params_.app_version, cfu_params_.omaha_url, cfu_params_.flags)); + + // Verify + EXPECT_EQ(cfu_params_.expected_forced_app_version, + attempter_.forced_app_version()); + EXPECT_EQ(cfu_params_.expected_forced_omaha_url, + attempter_.forced_omaha_url()); + EXPECT_EQ(cfu_params_.should_schedule_updates_be_called, + attempter_.WasScheduleUpdatesCalled()); +} + +void UpdateAttempterTest::TestProcessingDone() { + // Setup + attempter_.DisableScheduleUpdates(); + attempter_.is_install_ = pd_params_.is_install; + attempter_.status_ = pd_params_.status; + attempter_.omaha_request_params_->set_dlc_apps_params( + pd_params_.dlc_apps_params); + + // Expects + if (pd_params_.should_install_completed_be_called) + EXPECT_CALL(mock_dlcservice_, + InstallCompleted(pd_params_.args_to_install_completed)) + .WillOnce(Return(true)); + else + EXPECT_CALL(mock_dlcservice_, InstallCompleted(_)).Times(0); + if (pd_params_.should_update_completed_be_called) + EXPECT_CALL(mock_dlcservice_, + UpdateCompleted(pd_params_.args_to_update_completed)) + .WillOnce(Return(true)); + else + EXPECT_CALL(mock_dlcservice_, UpdateCompleted(_)).Times(0); + + // Invocation + attempter_.ProcessingDone(pd_params_.processor, pd_params_.code); + + // Verify + EXPECT_EQ(pd_params_.kExpectedIsInstall, attempter_.is_install_); + EXPECT_EQ(pd_params_.should_schedule_updates_be_called, + attempter_.WasScheduleUpdatesCalled()); + EXPECT_EQ(pd_params_.expected_exit_status, attempter_.status_); +} + +void UpdateAttempterTest::ScheduleQuitMainLoop() { + loop_.PostTask( + FROM_HERE, + base::Bind([](brillo::BaseMessageLoop* loop) { loop->BreakLoop(); }, + base::Unretained(&loop_))); +} + +void UpdateAttempterTest::SessionIdTestChange() { + EXPECT_NE(UpdateStatus::UPDATED_NEED_REBOOT, attempter_.status()); + const auto old_session_id = attempter_.session_id_; + attempter_.Update({}); + EXPECT_NE(old_session_id, attempter_.session_id_); + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, SessionIdTestChange) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::SessionIdTestChange, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::SessionIdTestEnforceEmptyStrPingOmaha() { + // The |session_id_| should not be changed and should remain as an empty + // string when |status_| is |UPDATED_NEED_REBOOT| (only for consistency) + // and |PingOmaha()| is called. + attempter_.DisableScheduleUpdates(); + attempter_.status_ = UpdateStatus::UPDATED_NEED_REBOOT; + const auto old_session_id = attempter_.session_id_; + auto CheckIfEmptySessionId = [](AbstractAction* aa) { + if (aa->Type() == OmahaRequestAction::StaticType()) { + EXPECT_TRUE(static_cast<OmahaRequestAction*>(aa)->session_id_.empty()); + } + }; + EXPECT_CALL(*processor_, EnqueueAction(Pointee(_))) + .WillRepeatedly(Invoke(CheckIfEmptySessionId)); + EXPECT_CALL(*processor_, StartProcessing()); + attempter_.PingOmaha(); + EXPECT_EQ(old_session_id, attempter_.session_id_); + EXPECT_EQ(UpdateStatus::UPDATED_NEED_REBOOT, attempter_.status_); + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, SessionIdTestEnforceEmptyStrPingOmaha) { + loop_.PostTask( + FROM_HERE, + base::Bind(&UpdateAttempterTest::SessionIdTestEnforceEmptyStrPingOmaha, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::SessionIdTestConsistencyInUpdateFlow() { + // All session IDs passed into |OmahaRequestActions| should be enforced to + // have the same value in |BuildUpdateActions()|. + unordered_set<string> session_ids; + // Gather all the session IDs being passed to |OmahaRequestActions|. + auto CheckSessionId = [&session_ids](AbstractAction* aa) { + if (aa->Type() == OmahaRequestAction::StaticType()) + session_ids.insert(static_cast<OmahaRequestAction*>(aa)->session_id_); + }; + EXPECT_CALL(*processor_, EnqueueAction(Pointee(_))) + .WillRepeatedly(Invoke(CheckSessionId)); + attempter_.BuildUpdateActions(false); + // Validate that all the session IDs are the same. + EXPECT_EQ(1, session_ids.size()); + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, SessionIdTestConsistencyInUpdateFlow) { + loop_.PostTask( + FROM_HERE, + base::Bind(&UpdateAttempterTest::SessionIdTestConsistencyInUpdateFlow, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::SessionIdTestInDownloadAction() { + // The session ID passed into |DownloadAction|'s |LibcurlHttpFetcher| should + // be enforced to be included in the HTTP header as X-Goog-Update-SessionId. + string header_value; + auto CheckSessionIdInDownloadAction = [&header_value](AbstractAction* aa) { + if (aa->Type() == DownloadActionChromeos::StaticType()) { + DownloadActionChromeos* da = static_cast<DownloadActionChromeos*>(aa); + EXPECT_TRUE(da->http_fetcher()->GetHeader(kXGoogleUpdateSessionId, + &header_value)); + } + }; + EXPECT_CALL(*processor_, EnqueueAction(Pointee(_))) + .WillRepeatedly(Invoke(CheckSessionIdInDownloadAction)); + attempter_.BuildUpdateActions(false); + // Validate that X-Goog-Update_SessionId is set correctly in HTTP Header. + EXPECT_EQ(attempter_.session_id_, header_value); + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, SessionIdTestInDownloadAction) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::SessionIdTestInDownloadAction, + base::Unretained(this))); + loop_.Run(); +} + +TEST_F(UpdateAttempterTest, ActionCompletedDownloadTest) { + unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr)); + fetcher->FailTransfer(503); // Sets the HTTP response code. + DownloadActionChromeos action( + prefs_, nullptr, nullptr, fetcher.release(), false /* interactive */); + attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess); + EXPECT_FALSE(prefs_->Exists(kPrefsDeltaUpdateFailures)); + EXPECT_EQ(UpdateStatus::FINALIZING, attempter_.status()); + EXPECT_EQ(0.0, attempter_.download_progress_); + ASSERT_EQ(nullptr, attempter_.error_event_.get()); +} + +TEST_F(UpdateAttempterTest, ActionCompletedErrorTest) { + MockAction action; + EXPECT_CALL(action, Type()).WillRepeatedly(Return("MockAction")); + attempter_.status_ = UpdateStatus::DOWNLOADING; + attempter_.ActionCompleted(nullptr, &action, ErrorCode::kError); + ASSERT_NE(nullptr, attempter_.error_event_.get()); +} + +TEST_F(UpdateAttempterTest, DownloadProgressAccumulationTest) { + // Simple test case, where all the values match (nothing was skipped) + uint64_t bytes_progressed_1 = 1024 * 1024; // 1MB + uint64_t bytes_progressed_2 = 1024 * 1024; // 1MB + uint64_t bytes_received_1 = bytes_progressed_1; + uint64_t bytes_received_2 = bytes_received_1 + bytes_progressed_2; + uint64_t bytes_total = 20 * 1024 * 1024; // 20MB + + double progress_1 = + static_cast<double>(bytes_received_1) / static_cast<double>(bytes_total); + double progress_2 = + static_cast<double>(bytes_received_2) / static_cast<double>(bytes_total); + + EXPECT_EQ(0.0, attempter_.download_progress_); + // This is set via inspecting the InstallPlan payloads when the + // |OmahaResponseAction| is completed. + attempter_.new_payload_size_ = bytes_total; + NiceMock<MockServiceObserver> observer; + EXPECT_CALL(observer, + SendStatusUpdate(AllOf( + Field(&UpdateEngineStatus::progress, progress_1), + Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING), + Field(&UpdateEngineStatus::new_size_bytes, bytes_total)))); + EXPECT_CALL(observer, + SendStatusUpdate(AllOf( + Field(&UpdateEngineStatus::progress, progress_2), + Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING), + Field(&UpdateEngineStatus::new_size_bytes, bytes_total)))); + attempter_.AddObserver(&observer); + attempter_.BytesReceived(bytes_progressed_1, bytes_received_1, bytes_total); + EXPECT_EQ(progress_1, attempter_.download_progress_); + // This iteration validates that a later set of updates to the variables are + // properly handled (so that |getStatus()| will return the same progress info + // as the callback is receiving. + attempter_.BytesReceived(bytes_progressed_2, bytes_received_2, bytes_total); + EXPECT_EQ(progress_2, attempter_.download_progress_); +} + +TEST_F(UpdateAttempterTest, ChangeToDownloadingOnReceivedBytesTest) { + // The transition into |UpdateStatus::DOWNLOADING| happens when the + // first bytes are received. + uint64_t bytes_progressed = 1024 * 1024; // 1MB + uint64_t bytes_received = 2 * 1024 * 1024; // 2MB + uint64_t bytes_total = 20 * 1024 * 1024; // 300MB + attempter_.status_ = UpdateStatus::CHECKING_FOR_UPDATE; + // This is set via inspecting the InstallPlan payloads when the + // |OmahaResponseAction| is completed. + attempter_.new_payload_size_ = bytes_total; + EXPECT_EQ(0.0, attempter_.download_progress_); + NiceMock<MockServiceObserver> observer; + EXPECT_CALL(observer, + SendStatusUpdate(AllOf( + Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING), + Field(&UpdateEngineStatus::new_size_bytes, bytes_total)))); + attempter_.AddObserver(&observer); + attempter_.BytesReceived(bytes_progressed, bytes_received, bytes_total); + EXPECT_EQ(UpdateStatus::DOWNLOADING, attempter_.status_); +} + +TEST_F(UpdateAttempterTest, BroadcastCompleteDownloadTest) { + // There is a special case to ensure that at 100% downloaded, + // |download_progress_| is updated and broadcastest. + uint64_t bytes_progressed = 0; // ignored + uint64_t bytes_received = 5 * 1024 * 1024; // ignored + uint64_t bytes_total = 5 * 1024 * 1024; // 300MB + attempter_.status_ = UpdateStatus::DOWNLOADING; + attempter_.new_payload_size_ = bytes_total; + EXPECT_EQ(0.0, attempter_.download_progress_); + NiceMock<MockServiceObserver> observer; + EXPECT_CALL(observer, + SendStatusUpdate(AllOf( + Field(&UpdateEngineStatus::progress, 1.0), + Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING), + Field(&UpdateEngineStatus::new_size_bytes, bytes_total)))); + attempter_.AddObserver(&observer); + attempter_.BytesReceived(bytes_progressed, bytes_received, bytes_total); + EXPECT_EQ(1.0, attempter_.download_progress_); +} + +TEST_F(UpdateAttempterTest, ActionCompletedOmahaRequestTest) { + unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr)); + fetcher->FailTransfer(500); // Sets the HTTP response code. + OmahaRequestAction action(nullptr, std::move(fetcher), false, ""); + ObjectCollectorAction<OmahaResponse> collector_action; + BondActions(&action, &collector_action); + OmahaResponse response; + response.poll_interval = 234; + action.SetOutputObject(response); + attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess); + EXPECT_FALSE(prefs_->Exists(kPrefsDeltaUpdateFailures)); + EXPECT_EQ(500, attempter_.http_response_code()); + EXPECT_EQ(UpdateStatus::IDLE, attempter_.status()); + EXPECT_EQ(234U, attempter_.server_dictated_poll_interval_); + ASSERT_TRUE(attempter_.error_event_.get() == nullptr); +} + +TEST_F(UpdateAttempterTest, ConstructWithUpdatedMarkerTest) { + string boot_id; + EXPECT_TRUE(utils::GetBootId(&boot_id)); + FakeSystemState::Get()->fake_prefs()->SetString(kPrefsUpdateCompletedOnBootId, + boot_id); + attempter_.Init(); + EXPECT_EQ(UpdateStatus::UPDATED_NEED_REBOOT, attempter_.status()); +} + +TEST_F(UpdateAttempterTest, GetErrorCodeForActionTest) { + EXPECT_EQ(ErrorCode::kSuccess, + GetErrorCodeForAction(nullptr, ErrorCode::kSuccess)); + + OmahaRequestAction omaha_request_action(nullptr, nullptr, false, ""); + EXPECT_EQ(ErrorCode::kOmahaRequestError, + GetErrorCodeForAction(&omaha_request_action, ErrorCode::kError)); + OmahaResponseHandlerAction omaha_response_handler_action; + EXPECT_EQ( + ErrorCode::kOmahaResponseHandlerError, + GetErrorCodeForAction(&omaha_response_handler_action, ErrorCode::kError)); + DynamicPartitionControlStub dynamic_control_stub; + FilesystemVerifierAction filesystem_verifier_action(&dynamic_control_stub); + EXPECT_EQ( + ErrorCode::kFilesystemVerifierError, + GetErrorCodeForAction(&filesystem_verifier_action, ErrorCode::kError)); + PostinstallRunnerAction postinstall_runner_action( + FakeSystemState::Get()->fake_boot_control(), + FakeSystemState::Get()->fake_hardware()); + EXPECT_EQ( + ErrorCode::kPostinstallRunnerError, + GetErrorCodeForAction(&postinstall_runner_action, ErrorCode::kError)); + MockAction action_mock; + EXPECT_CALL(action_mock, Type()).WillOnce(Return("MockAction")); + EXPECT_EQ(ErrorCode::kError, + GetErrorCodeForAction(&action_mock, ErrorCode::kError)); +} + +TEST_F(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest) { + attempter_.omaha_request_params_->set_delta_okay(true); + attempter_.DisableDeltaUpdateIfNeeded(); + EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay()); + prefs_->SetInt64(kPrefsDeltaUpdateFailures, + UpdateAttempter::kMaxDeltaUpdateFailures - 1); + attempter_.DisableDeltaUpdateIfNeeded(); + EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay()); + prefs_->SetInt64(kPrefsDeltaUpdateFailures, + UpdateAttempter::kMaxDeltaUpdateFailures); + attempter_.DisableDeltaUpdateIfNeeded(); + EXPECT_FALSE(attempter_.omaha_request_params_->delta_okay()); + attempter_.DisableDeltaUpdateIfNeeded(); + EXPECT_FALSE(attempter_.omaha_request_params_->delta_okay()); +} + +TEST_F(UpdateAttempterTest, MarkDeltaUpdateFailureTest) { + attempter_.MarkDeltaUpdateFailure(); + + EXPECT_TRUE(prefs_->SetInt64(kPrefsDeltaUpdateFailures, -1)); + attempter_.MarkDeltaUpdateFailure(); + int64_t value = 0; + EXPECT_TRUE(prefs_->GetInt64(kPrefsDeltaUpdateFailures, &value)); + EXPECT_EQ(value, 1); + + attempter_.MarkDeltaUpdateFailure(); + EXPECT_TRUE(prefs_->GetInt64(kPrefsDeltaUpdateFailures, &value)); + EXPECT_EQ(value, 2); + + EXPECT_TRUE(prefs_->SetInt64(kPrefsDeltaUpdateFailures, + UpdateAttempter::kMaxDeltaUpdateFailures)); + attempter_.MarkDeltaUpdateFailure(); + EXPECT_TRUE(prefs_->GetInt64(kPrefsDeltaUpdateFailures, &value)); + EXPECT_EQ(value, UpdateAttempter::kMaxDeltaUpdateFailures + 1); +} + +TEST_F(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest) { + EXPECT_CALL(*processor_, EnqueueAction(_)).Times(0); + EXPECT_CALL(*processor_, StartProcessing()).Times(0); + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), UpdateFailed(_)) + .Times(0); + OmahaResponse response; + string url1 = "http://url1"; + response.packages.push_back({.payload_urls = {url1, "https://url"}}); + EXPECT_CALL(*(FakeSystemState::Get()->mock_payload_state()), GetCurrentUrl()) + .WillRepeatedly(Return(url1)); + FakeSystemState::Get()->mock_payload_state()->SetResponse(response); + attempter_.ScheduleErrorEventAction(); + EXPECT_EQ(url1, + FakeSystemState::Get()->mock_payload_state()->GetCurrentUrl()); +} + +TEST_F(UpdateAttempterTest, ScheduleErrorEventActionTest) { + EXPECT_CALL(*processor_, + EnqueueAction(Pointee(Property( + &AbstractAction::Type, OmahaRequestAction::StaticType())))); + EXPECT_CALL(*processor_, StartProcessing()); + ErrorCode err = ErrorCode::kError; + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), UpdateFailed(err)); + attempter_.error_event_.reset(new OmahaEvent( + OmahaEvent::kTypeUpdateComplete, OmahaEvent::kResultError, err)); + attempter_.ScheduleErrorEventAction(); + EXPECT_EQ(UpdateStatus::REPORTING_ERROR_EVENT, attempter_.status()); +} + +namespace { +// Actions that will be built as part of an update check. +vector<string> GetUpdateActionTypes() { + return {OmahaRequestAction::StaticType(), + OmahaResponseHandlerAction::StaticType(), + UpdateBootFlagsAction::StaticType(), + OmahaRequestAction::StaticType(), + DownloadActionChromeos::StaticType(), + OmahaRequestAction::StaticType(), + FilesystemVerifierAction::StaticType(), + PostinstallRunnerAction::StaticType(), + OmahaRequestAction::StaticType()}; +} + +// Actions that will be built as part of a user-initiated rollback. +vector<string> GetRollbackActionTypes() { + return {InstallPlanAction::StaticType(), + PostinstallRunnerAction::StaticType()}; +} + +const StagingSchedule kValidStagingSchedule = { + {4, 10}, {10, 40}, {19, 70}, {26, 100}}; + +} // namespace + +void UpdateAttempterTest::UpdateTestStart() { + attempter_.set_http_response_code(200); + + // Expect that the device policy is loaded by the |UpdateAttempter| at some + // point by calling |RefreshDevicePolicy()|. + auto device_policy = std::make_unique<policy::MockDevicePolicy>(); + EXPECT_CALL(*device_policy, LoadPolicy()) + .Times(testing::AtLeast(1)) + .WillRepeatedly(Return(true)); + attempter_.policy_provider_.reset( + new policy::PolicyProvider(std::move(device_policy))); + + { + InSequence s; + for (const auto& update_action_type : GetUpdateActionTypes()) { + EXPECT_CALL(*processor_, + EnqueueAction(Pointee( + Property(&AbstractAction::Type, update_action_type)))); + } + EXPECT_CALL(*processor_, StartProcessing()); + } + + attempter_.Update({}); + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::UpdateTestVerify, + base::Unretained(this))); +} + +void UpdateAttempterTest::UpdateTestVerify() { + EXPECT_EQ(0, attempter_.http_response_code()); + EXPECT_EQ(&attempter_, processor_->delegate()); + EXPECT_EQ(UpdateStatus::CHECKING_FOR_UPDATE, attempter_.status()); + loop_.BreakLoop(); +} + +void UpdateAttempterTest::RollbackTestStart(bool enterprise_rollback, + bool valid_slot) { + // Create a device policy so that we can change settings. + auto device_policy = std::make_unique<policy::MockDevicePolicy>(); + EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true)); + FakeSystemState::Get()->set_device_policy(device_policy.get()); + if (enterprise_rollback) { + // We return an empty owner as this is an enterprise. + EXPECT_CALL(*device_policy, GetOwner(_)) + .WillRepeatedly(DoAll(SetArgPointee<0>(string("")), Return(true))); + } else { + // We return a fake owner as this is an owned consumer device. + EXPECT_CALL(*device_policy, GetOwner(_)) + .WillRepeatedly(DoAll(SetArgPointee<0>(string("fake.mail@fake.com")), + Return(true))); + } + + attempter_.policy_provider_.reset( + new policy::PolicyProvider(std::move(device_policy))); + + if (valid_slot) { + BootControlInterface::Slot rollback_slot = 1; + LOG(INFO) << "Test Mark Bootable: " + << BootControlInterface::SlotName(rollback_slot); + FakeSystemState::Get()->fake_boot_control()->SetSlotBootable(rollback_slot, + true); + } + + bool is_rollback_allowed = false; + + // We only allow rollback on devices that are not enterprise enrolled and + // which have a valid slot to rollback to. + if (!enterprise_rollback && valid_slot) { + is_rollback_allowed = true; + } + + if (is_rollback_allowed) { + InSequence s; + for (const auto& rollback_action_type : GetRollbackActionTypes()) { + EXPECT_CALL(*processor_, + EnqueueAction(Pointee( + Property(&AbstractAction::Type, rollback_action_type)))); + } + EXPECT_CALL(*processor_, StartProcessing()); + + EXPECT_TRUE(attempter_.Rollback(true)); + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::RollbackTestVerify, + base::Unretained(this))); + } else { + EXPECT_FALSE(attempter_.Rollback(true)); + loop_.BreakLoop(); + } +} + +void UpdateAttempterTest::RollbackTestVerify() { + // Verifies the actions that were enqueued. + EXPECT_EQ(&attempter_, processor_->delegate()); + EXPECT_EQ(UpdateStatus::ATTEMPTING_ROLLBACK, attempter_.status()); + EXPECT_EQ(0U, attempter_.install_plan_->partitions.size()); + EXPECT_EQ(attempter_.install_plan_->powerwash_required, true); + loop_.BreakLoop(); +} + +TEST_F(UpdateAttempterTest, UpdateTest) { + UpdateTestStart(); + loop_.Run(); +} + +TEST_F(UpdateAttempterTest, RollbackTest) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::RollbackTestStart, + base::Unretained(this), + false, + true)); + loop_.Run(); +} + +TEST_F(UpdateAttempterTest, InvalidSlotRollbackTest) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::RollbackTestStart, + base::Unretained(this), + false, + false)); + loop_.Run(); +} + +TEST_F(UpdateAttempterTest, EnterpriseRollbackTest) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::RollbackTestStart, + base::Unretained(this), + true, + true)); + loop_.Run(); +} + +void UpdateAttempterTest::PingOmahaTestStart() { + EXPECT_CALL(*processor_, + EnqueueAction(Pointee(Property( + &AbstractAction::Type, OmahaRequestAction::StaticType())))); + EXPECT_CALL(*processor_, StartProcessing()); + attempter_.PingOmaha(); + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, PingOmahaTest) { + EXPECT_FALSE(attempter_.waiting_for_scheduled_check_); + EXPECT_FALSE(attempter_.WasScheduleUpdatesCalled()); + // Disable scheduling of subsequnet checks; we're using the |DefaultPolicy| in + // testing, which is more permissive than we want to handle here. + attempter_.DisableScheduleUpdates(); + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::PingOmahaTestStart, + base::Unretained(this))); + brillo::MessageLoopRunMaxIterations(&loop_, 100); + EXPECT_EQ(UpdateStatus::UPDATED_NEED_REBOOT, attempter_.status()); + EXPECT_TRUE(attempter_.WasScheduleUpdatesCalled()); +} + +TEST_F(UpdateAttempterTest, CreatePendingErrorEventTest) { + MockAction action; + const ErrorCode kCode = ErrorCode::kDownloadTransferError; + attempter_.CreatePendingErrorEvent(&action, kCode); + ASSERT_NE(nullptr, attempter_.error_event_.get()); + EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type); + EXPECT_EQ(OmahaEvent::kResultError, attempter_.error_event_->result); + EXPECT_EQ( + static_cast<ErrorCode>(static_cast<int>(kCode) | + static_cast<int>(ErrorCode::kTestOmahaUrlFlag)), + attempter_.error_event_->error_code); +} + +TEST_F(UpdateAttempterTest, CreatePendingErrorEventResumedTest) { + attempter_.install_plan_.reset(new InstallPlan); + attempter_.install_plan_->is_resume = true; + MockAction action; + const ErrorCode kCode = ErrorCode::kInstallDeviceOpenError; + attempter_.CreatePendingErrorEvent(&action, kCode); + ASSERT_NE(nullptr, attempter_.error_event_.get()); + EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type); + EXPECT_EQ(OmahaEvent::kResultError, attempter_.error_event_->result); + EXPECT_EQ( + static_cast<ErrorCode>(static_cast<int>(kCode) | + static_cast<int>(ErrorCode::kResumedFlag) | + static_cast<int>(ErrorCode::kTestOmahaUrlFlag)), + attempter_.error_event_->error_code); +} + +TEST_F(UpdateAttempterTest, P2PNotStartedAtStartupWhenNotEnabled) { + MockP2PManager mock_p2p_manager; + FakeSystemState::Get()->set_p2p_manager(&mock_p2p_manager); + mock_p2p_manager.fake().SetP2PEnabled(false); + EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning()).Times(0); + attempter_.UpdateEngineStarted(); +} + +TEST_F(UpdateAttempterTest, P2PNotStartedAtStartupWhenEnabledButNotSharing) { + MockP2PManager mock_p2p_manager; + FakeSystemState::Get()->set_p2p_manager(&mock_p2p_manager); + mock_p2p_manager.fake().SetP2PEnabled(true); + EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning()).Times(0); + attempter_.UpdateEngineStarted(); +} + +TEST_F(UpdateAttempterTest, P2PStartedAtStartupWhenEnabledAndSharing) { + MockP2PManager mock_p2p_manager; + FakeSystemState::Get()->set_p2p_manager(&mock_p2p_manager); + mock_p2p_manager.fake().SetP2PEnabled(true); + mock_p2p_manager.fake().SetCountSharedFilesResult(1); + EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning()); + attempter_.UpdateEngineStarted(); +} + +TEST_F(UpdateAttempterTest, P2PNotEnabled) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::P2PNotEnabledStart, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::P2PNotEnabledStart() { + // If P2P is not enabled, check that we do not attempt housekeeping + // and do not convey that P2P is to be used. + MockP2PManager mock_p2p_manager; + FakeSystemState::Get()->set_p2p_manager(&mock_p2p_manager); + mock_p2p_manager.fake().SetP2PEnabled(false); + EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()).Times(0); + attempter_.Update({}); + EXPECT_FALSE(actual_using_p2p_for_downloading_); + EXPECT_FALSE(actual_using_p2p_for_sharing()); + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, P2PEnabledStartingFails) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::P2PEnabledStartingFailsStart, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::P2PEnabledStartingFailsStart() { + // If P2P is enabled, but starting it fails ensure we don't do + // any housekeeping and do not convey that P2P should be used. + MockP2PManager mock_p2p_manager; + FakeSystemState::Get()->set_p2p_manager(&mock_p2p_manager); + mock_p2p_manager.fake().SetP2PEnabled(true); + mock_p2p_manager.fake().SetEnsureP2PRunningResult(false); + mock_p2p_manager.fake().SetPerformHousekeepingResult(false); + EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()).Times(0); + attempter_.Update({}); + EXPECT_FALSE(actual_using_p2p_for_downloading()); + EXPECT_FALSE(actual_using_p2p_for_sharing()); + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, P2PEnabledHousekeepingFails) { + loop_.PostTask( + FROM_HERE, + base::Bind(&UpdateAttempterTest::P2PEnabledHousekeepingFailsStart, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::P2PEnabledHousekeepingFailsStart() { + // If P2P is enabled, starting it works but housekeeping fails, ensure + // we do not convey P2P is to be used. + MockP2PManager mock_p2p_manager; + FakeSystemState::Get()->set_p2p_manager(&mock_p2p_manager); + mock_p2p_manager.fake().SetP2PEnabled(true); + mock_p2p_manager.fake().SetEnsureP2PRunningResult(true); + mock_p2p_manager.fake().SetPerformHousekeepingResult(false); + EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()); + attempter_.Update({}); + EXPECT_FALSE(actual_using_p2p_for_downloading()); + EXPECT_FALSE(actual_using_p2p_for_sharing()); + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, P2PEnabled) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::P2PEnabledStart, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::P2PEnabledStart() { + MockP2PManager mock_p2p_manager; + FakeSystemState::Get()->set_p2p_manager(&mock_p2p_manager); + // If P2P is enabled and starting it works, check that we performed + // housekeeping and that we convey P2P should be used. + mock_p2p_manager.fake().SetP2PEnabled(true); + mock_p2p_manager.fake().SetEnsureP2PRunningResult(true); + mock_p2p_manager.fake().SetPerformHousekeepingResult(true); + EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()); + attempter_.Update({}); + EXPECT_TRUE(actual_using_p2p_for_downloading()); + EXPECT_TRUE(actual_using_p2p_for_sharing()); + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, P2PEnabledInteractive) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::P2PEnabledInteractiveStart, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::P2PEnabledInteractiveStart() { + MockP2PManager mock_p2p_manager; + FakeSystemState::Get()->set_p2p_manager(&mock_p2p_manager); + // For an interactive check, if P2P is enabled and starting it + // works, check that we performed housekeeping and that we convey + // P2P should be used for sharing but NOT for downloading. + mock_p2p_manager.fake().SetP2PEnabled(true); + mock_p2p_manager.fake().SetEnsureP2PRunningResult(true); + mock_p2p_manager.fake().SetPerformHousekeepingResult(true); + EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()); + attempter_.Update({.interactive = true}); + EXPECT_FALSE(actual_using_p2p_for_downloading()); + EXPECT_TRUE(actual_using_p2p_for_sharing()); + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, ReadScatterFactorFromPolicy) { + loop_.PostTask( + FROM_HERE, + base::Bind(&UpdateAttempterTest::ReadScatterFactorFromPolicyTestStart, + base::Unretained(this))); + loop_.Run(); +} + +// Tests that the scatter_factor_in_seconds value is properly fetched +// from the device policy. +void UpdateAttempterTest::ReadScatterFactorFromPolicyTestStart() { + int64_t scatter_factor_in_seconds = 36000; + + auto device_policy = std::make_unique<policy::MockDevicePolicy>(); + EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true)); + FakeSystemState::Get()->set_device_policy(device_policy.get()); + + EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(scatter_factor_in_seconds), Return(true))); + + attempter_.policy_provider_.reset( + new policy::PolicyProvider(std::move(device_policy))); + + attempter_.Update({}); + EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds()); + + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, DecrementUpdateCheckCountTest) { + loop_.PostTask( + FROM_HERE, + base::Bind(&UpdateAttempterTest::DecrementUpdateCheckCountTestStart, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::DecrementUpdateCheckCountTestStart() { + // Tests that the scatter_factor_in_seconds value is properly fetched + // from the device policy and is decremented if value > 0. + int64_t initial_value = 5; + auto* fake_prefs = FakeSystemState::Get()->fake_prefs(); + FakeSystemState::Get()->fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch()); + + EXPECT_TRUE(fake_prefs->SetInt64(kPrefsUpdateCheckCount, initial_value)); + + int64_t scatter_factor_in_seconds = 10; + + auto device_policy = std::make_unique<policy::MockDevicePolicy>(); + EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true)); + FakeSystemState::Get()->set_device_policy(device_policy.get()); + + EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(scatter_factor_in_seconds), Return(true))); + + attempter_.policy_provider_.reset( + new policy::PolicyProvider(std::move(device_policy))); + + attempter_.Update({}); + EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds()); + + // Make sure the file still exists. + EXPECT_TRUE(fake_prefs->Exists(kPrefsUpdateCheckCount)); + + int64_t new_value; + EXPECT_TRUE(fake_prefs->GetInt64(kPrefsUpdateCheckCount, &new_value)); + EXPECT_EQ(initial_value - 1, new_value); + + EXPECT_TRUE( + attempter_.omaha_request_params_->update_check_count_wait_enabled()); + + // However, if the count is already 0, it's not decremented. Test that. + initial_value = 0; + EXPECT_TRUE(fake_prefs->SetInt64(kPrefsUpdateCheckCount, initial_value)); + attempter_.Update({}); + EXPECT_TRUE(fake_prefs->Exists(kPrefsUpdateCheckCount)); + EXPECT_TRUE(fake_prefs->GetInt64(kPrefsUpdateCheckCount, &new_value)); + EXPECT_EQ(initial_value, new_value); + + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, NoScatteringDoneDuringManualUpdateTestStart) { + loop_.PostTask( + FROM_HERE, + base::Bind( + &UpdateAttempterTest::NoScatteringDoneDuringManualUpdateTestStart, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::NoScatteringDoneDuringManualUpdateTestStart() { + // Tests that no scattering logic is enabled if the update check + // is manually done (as opposed to a scheduled update check) + int64_t initial_value = 8; + auto* fake_prefs = FakeSystemState::Get()->fake_prefs(); + FakeSystemState::Get()->fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch()); + + EXPECT_TRUE( + fake_prefs->SetInt64(kPrefsWallClockScatteringWaitPeriod, initial_value)); + EXPECT_TRUE(fake_prefs->SetInt64(kPrefsUpdateCheckCount, initial_value)); + + // make sure scatter_factor is non-zero as scattering is disabled + // otherwise. + int64_t scatter_factor_in_seconds = 50; + + auto device_policy = std::make_unique<policy::MockDevicePolicy>(); + EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true)); + FakeSystemState::Get()->set_device_policy(device_policy.get()); + + EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(scatter_factor_in_seconds), Return(true))); + + attempter_.policy_provider_.reset( + new policy::PolicyProvider(std::move(device_policy))); + + // Trigger an interactive check so we can test that scattering is disabled. + attempter_.Update({.interactive = true}); + EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds()); + + // Make sure scattering is disabled for manual (i.e. user initiated) update + // checks and all artifacts are removed. + EXPECT_FALSE( + attempter_.omaha_request_params_->wall_clock_based_wait_enabled()); + EXPECT_FALSE(fake_prefs->Exists(kPrefsWallClockScatteringWaitPeriod)); + EXPECT_EQ(0, attempter_.omaha_request_params_->waiting_period().InSeconds()); + EXPECT_FALSE( + attempter_.omaha_request_params_->update_check_count_wait_enabled()); + EXPECT_FALSE(fake_prefs->Exists(kPrefsUpdateCheckCount)); + + ScheduleQuitMainLoop(); +} + +void UpdateAttempterTest::SetUpStagingTest(const StagingSchedule& schedule) { + int64_t initial_value = 8; + EXPECT_TRUE( + prefs_->SetInt64(kPrefsWallClockScatteringWaitPeriod, initial_value)); + EXPECT_TRUE(prefs_->SetInt64(kPrefsUpdateCheckCount, initial_value)); + attempter_.scatter_factor_ = TimeDelta::FromSeconds(20); + + auto device_policy = std::make_unique<policy::MockDevicePolicy>(); + EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true)); + FakeSystemState::Get()->set_device_policy(device_policy.get()); + EXPECT_CALL(*device_policy, GetDeviceUpdateStagingSchedule(_)) + .WillRepeatedly(DoAll(SetArgPointee<0>(schedule), Return(true))); + + attempter_.policy_provider_.reset( + new policy::PolicyProvider(std::move(device_policy))); +} + +TEST_F(UpdateAttempterTest, StagingSetsPrefsAndTurnsOffScattering) { + loop_.PostTask( + FROM_HERE, + base::Bind( + &UpdateAttempterTest::StagingSetsPrefsAndTurnsOffScatteringStart, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::StagingSetsPrefsAndTurnsOffScatteringStart() { + // Tests that staging sets its prefs properly and turns off scattering. + FakeSystemState::Get()->fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch()); + SetUpStagingTest(kValidStagingSchedule); + + attempter_.Update({}); + auto* fake_prefs = FakeSystemState::Get()->fake_prefs(); + // Check that prefs have the correct values. + int64_t update_count; + EXPECT_TRUE(fake_prefs->GetInt64(kPrefsUpdateCheckCount, &update_count)); + int64_t waiting_time_days; + EXPECT_TRUE(fake_prefs->GetInt64(kPrefsWallClockStagingWaitPeriod, + &waiting_time_days)); + EXPECT_GT(waiting_time_days, 0); + // Update count should have been decremented. + EXPECT_EQ(7, update_count); + // Check that Omaha parameters were updated correctly. + EXPECT_TRUE( + attempter_.omaha_request_params_->update_check_count_wait_enabled()); + EXPECT_TRUE( + attempter_.omaha_request_params_->wall_clock_based_wait_enabled()); + EXPECT_EQ(waiting_time_days, + attempter_.omaha_request_params_->waiting_period().InDays()); + // Check class variables. + EXPECT_EQ(waiting_time_days, attempter_.staging_wait_time_.InDays()); + EXPECT_EQ(kValidStagingSchedule, attempter_.staging_schedule_); + // Check that scattering is turned off + EXPECT_EQ(0, attempter_.scatter_factor_.InSeconds()); + EXPECT_FALSE(fake_prefs->Exists(kPrefsWallClockScatteringWaitPeriod)); + + ScheduleQuitMainLoop(); +} + +void UpdateAttempterTest::CheckStagingOff() { + // Check that all prefs were removed. + EXPECT_FALSE(prefs_->Exists(kPrefsUpdateCheckCount)); + EXPECT_FALSE(prefs_->Exists(kPrefsWallClockScatteringWaitPeriod)); + EXPECT_FALSE(prefs_->Exists(kPrefsWallClockStagingWaitPeriod)); + // Check that the Omaha parameters have the correct value. + EXPECT_EQ(0, attempter_.omaha_request_params_->waiting_period().InDays()); + EXPECT_EQ(attempter_.omaha_request_params_->waiting_period(), + attempter_.staging_wait_time_); + EXPECT_FALSE( + attempter_.omaha_request_params_->update_check_count_wait_enabled()); + EXPECT_FALSE( + attempter_.omaha_request_params_->wall_clock_based_wait_enabled()); + // Check that scattering is turned off too. + EXPECT_EQ(0, attempter_.scatter_factor_.InSeconds()); +} + +TEST_F(UpdateAttempterTest, StagingOffIfInteractive) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::StagingOffIfInteractiveStart, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::StagingOffIfInteractiveStart() { + // Tests that staging is turned off when an interactive update is requested. + FakeSystemState::Get()->fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch()); + SetUpStagingTest(kValidStagingSchedule); + + attempter_.Update({.interactive = true}); + CheckStagingOff(); + + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, StagingOffIfOobe) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::StagingOffIfOobeStart, + base::Unretained(this))); + loop_.Run(); +} + +void UpdateAttempterTest::StagingOffIfOobeStart() { + // Tests that staging is turned off if OOBE hasn't been completed. + FakeSystemState::Get()->fake_hardware()->SetIsOOBEEnabled(true); + FakeSystemState::Get()->fake_hardware()->UnsetIsOOBEComplete(); + SetUpStagingTest(kValidStagingSchedule); + + attempter_.Update({.interactive = true}); + CheckStagingOff(); + + ScheduleQuitMainLoop(); +} + +// Checks that we only report daily metrics at most every 24 hours. +TEST_F(UpdateAttempterTest, ReportDailyMetrics) { + auto* fake_clock = FakeSystemState::Get()->fake_clock(); + Time epoch = Time::FromInternalValue(0); + fake_clock->SetWallclockTime(epoch); + + // If there is no kPrefsDailyMetricsLastReportedAt state variable, + // we should report. + EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics()); + // We should not report again if no time has passed. + EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); + + // We should not report if only 10 hours has passed. + fake_clock->SetWallclockTime(epoch + TimeDelta::FromHours(10)); + EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); + + // We should not report if only 24 hours - 1 sec has passed. + fake_clock->SetWallclockTime(epoch + TimeDelta::FromHours(24) - + TimeDelta::FromSeconds(1)); + EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); + + // We should report if 24 hours has passed. + fake_clock->SetWallclockTime(epoch + TimeDelta::FromHours(24)); + EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics()); + + // But then we should not report again.. + EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); + + // .. until another 24 hours has passed + fake_clock->SetWallclockTime(epoch + TimeDelta::FromHours(47)); + EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); + fake_clock->SetWallclockTime(epoch + TimeDelta::FromHours(48)); + EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics()); + EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); + + // .. and another 24 hours + fake_clock->SetWallclockTime(epoch + TimeDelta::FromHours(71)); + EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); + fake_clock->SetWallclockTime(epoch + TimeDelta::FromHours(72)); + EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics()); + EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); + + // If the span between time of reporting and present time is + // negative, we report. This is in order to reset the timestamp and + // avoid an edge condition whereby a distant point in the future is + // in the state variable resulting in us never ever reporting again. + fake_clock->SetWallclockTime(epoch + TimeDelta::FromHours(71)); + EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics()); + EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); + + // In this case we should not update until the clock reads 71 + 24 = 95. + // Check that. + fake_clock->SetWallclockTime(epoch + TimeDelta::FromHours(94)); + EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); + fake_clock->SetWallclockTime(epoch + TimeDelta::FromHours(95)); + EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics()); + EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); +} + +TEST_F(UpdateAttempterTest, BootTimeInUpdateMarkerFile) { + FakeSystemState::Get()->fake_clock()->SetBootTime(Time::FromTimeT(42)); + attempter_.Init(); + + Time boot_time; + EXPECT_FALSE(attempter_.GetBootTimeAtUpdate(&boot_time)); + + attempter_.WriteUpdateCompletedMarker(); + + EXPECT_TRUE(attempter_.GetBootTimeAtUpdate(&boot_time)); + EXPECT_EQ(boot_time.ToTimeT(), 42); +} + +TEST_F(UpdateAttempterTest, AnyUpdateSourceAllowedUnofficial) { + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(false); + EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed()); +} + +TEST_F(UpdateAttempterTest, AnyUpdateSourceAllowedOfficialDevmode) { + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(true); + FakeSystemState::Get()->fake_hardware()->SetAreDevFeaturesEnabled(true); + EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed()); +} + +TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedOfficialNormal) { + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(true); + FakeSystemState::Get()->fake_hardware()->SetAreDevFeaturesEnabled(false); + EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed()); +} + +// TODO(kimjae): Follow testing pattern with params for |CheckForInstall()|. +// When adding, remove older tests related to |CheckForInstall()|. +TEST_F(UpdateAttempterTest, CheckForInstallNotIdleFails) { + for (const auto status : kNonIdleUpdateStatuses) { + // GIVEN a non-idle status. + attempter_.status_ = status; + + EXPECT_FALSE(attempter_.CheckForInstall({}, "")); + } +} + +TEST_F(UpdateAttempterTest, CheckForUpdateNotIdleFails) { + for (const auto status : kNonIdleUpdateStatuses) { + // GIVEN a non-idle status. + cfu_params_.status = status; + + // THEN |ScheduleUpdates()| should not be called. + cfu_params_.should_schedule_updates_be_called = false; + // THEN result should indicate failure. + cfu_params_.expected_result = false; + + TestCheckForUpdate(); + } +} + +TEST_F(UpdateAttempterTest, CheckForUpdateOfficalBuildClearsSource) { + // GIVEN a official build. + + // THEN we except forced app version + forced omaha url to be cleared. + + TestCheckForUpdate(); +} + +TEST_F(UpdateAttempterTest, CheckForUpdateUnofficialBuildChangesSource) { + // GIVEN a nonofficial build with dev features enabled. + cfu_params_.is_official_build = false; + cfu_params_.are_dev_features_enabled = true; + + // THEN the forced app version + forced omaha url changes based on input. + cfu_params_.expected_forced_app_version = cfu_params_.app_version; + cfu_params_.expected_forced_omaha_url = cfu_params_.omaha_url; + + TestCheckForUpdate(); +} + +TEST_F(UpdateAttempterTest, CheckForUpdateOfficialBuildScheduledAUTest) { + // GIVEN a scheduled autest omaha url. + cfu_params_.omaha_url = "autest-scheduled"; + + // THEN forced app version is cleared. + // THEN forced omaha url changes to default constant. + cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL; + + TestCheckForUpdate(); +} + +TEST_F(UpdateAttempterTest, CheckForUpdateUnofficialBuildScheduledAUTest) { + // GIVEN a scheduled autest omaha url. + cfu_params_.omaha_url = "autest-scheduled"; + // GIVEN a nonofficial build with dev features enabled. + cfu_params_.is_official_build = false; + cfu_params_.are_dev_features_enabled = true; + + // THEN forced app version changes based on input. + cfu_params_.expected_forced_app_version = cfu_params_.app_version; + // THEN forced omaha url changes to default constant. + cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL; + + TestCheckForUpdate(); +} + +TEST_F(UpdateAttempterTest, CheckForUpdateOfficialBuildAUTest) { + // GIVEN a autest omaha url. + cfu_params_.omaha_url = "autest"; + + // THEN forced app version is cleared. + // THEN forced omaha url changes to default constant. + cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL; + + TestCheckForUpdate(); +} + +TEST_F(UpdateAttempterTest, CheckForUpdateUnofficialBuildAUTest) { + // GIVEN a autest omha url. + cfu_params_.omaha_url = "autest"; + // GIVEN a nonofficial build with dev features enabled. + cfu_params_.is_official_build = false; + cfu_params_.are_dev_features_enabled = true; + + // THEN forced app version changes based on input. + cfu_params_.expected_forced_app_version = cfu_params_.app_version; + // THEN forced omaha url changes to default constant. + cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL; + + TestCheckForUpdate(); +} + +TEST_F(UpdateAttempterTest, + CheckForUpdateNonInteractiveOfficialBuildScheduledAUTest) { + // GIVEN a scheduled autest omaha url. + cfu_params_.omaha_url = "autest-scheduled"; + // GIVEN a noninteractive update. + cfu_params_.flags = UpdateAttemptFlags::kFlagNonInteractive; + + // THEN forced app version is cleared. + // THEN forced omaha url changes to default constant. + cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL; + + TestCheckForUpdate(); +} + +TEST_F(UpdateAttempterTest, + CheckForUpdateNonInteractiveUnofficialBuildScheduledAUTest) { + // GIVEN a scheduled autest omaha url. + cfu_params_.omaha_url = "autest-scheduled"; + // GIVEN a noninteractive update. + cfu_params_.flags = UpdateAttemptFlags::kFlagNonInteractive; + // GIVEN a nonofficial build with dev features enabled. + cfu_params_.is_official_build = false; + cfu_params_.are_dev_features_enabled = true; + + // THEN forced app version changes based on input. + cfu_params_.expected_forced_app_version = cfu_params_.app_version; + // THEN forced omaha url changes to default constant. + cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL; + + TestCheckForUpdate(); +} + +TEST_F(UpdateAttempterTest, CheckForUpdateNonInteractiveOfficialBuildAUTest) { + // GIVEN a autest omaha url. + cfu_params_.omaha_url = "autest"; + // GIVEN a noninteractive update. + cfu_params_.flags = UpdateAttemptFlags::kFlagNonInteractive; + + // THEN forced app version is cleared. + // THEN forced omaha url changes to default constant. + cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL; + + TestCheckForUpdate(); +} + +TEST_F(UpdateAttempterTest, CheckForUpdateNonInteractiveUnofficialBuildAUTest) { + // GIVEN a autest omaha url. + cfu_params_.omaha_url = "autest"; + // GIVEN a noninteractive update. + cfu_params_.flags = UpdateAttemptFlags::kFlagNonInteractive; + // GIVEN a nonofficial build with dev features enabled. + cfu_params_.is_official_build = false; + cfu_params_.are_dev_features_enabled = true; + + // THEN forced app version changes based on input. + cfu_params_.expected_forced_app_version = cfu_params_.app_version; + // THEN forced omaha url changes to default constant. + cfu_params_.expected_forced_omaha_url = constants::kOmahaDefaultAUTestURL; + + TestCheckForUpdate(); +} + +TEST_F(UpdateAttempterTest, CheckForUpdateMissingForcedCallback1) { + // GIVEN a official build. + // GIVEN forced callback is not set. + attempter_.set_forced_update_pending_callback(nullptr); + + // THEN we except forced app version + forced omaha url to be cleared. + // THEN |ScheduleUpdates()| should not be called. + cfu_params_.should_schedule_updates_be_called = false; + + TestCheckForUpdate(); +} + +TEST_F(UpdateAttempterTest, CheckForUpdateMissingForcedCallback2) { + // GIVEN a nonofficial build with dev features enabled. + cfu_params_.is_official_build = false; + cfu_params_.are_dev_features_enabled = true; + // GIVEN forced callback is not set. + attempter_.set_forced_update_pending_callback(nullptr); + + // THEN the forced app version + forced omaha url changes based on input. + cfu_params_.expected_forced_app_version = cfu_params_.app_version; + cfu_params_.expected_forced_omaha_url = cfu_params_.omaha_url; + // THEN |ScheduleUpdates()| should not be called. + cfu_params_.should_schedule_updates_be_called = false; + + TestCheckForUpdate(); +} + +TEST_F(UpdateAttempterTest, CheckForInstallTest) { + FakeSystemState::Get()->fake_hardware()->SetIsOfficialBuild(true); + FakeSystemState::Get()->fake_hardware()->SetAreDevFeaturesEnabled(false); + attempter_.CheckForInstall({}, "autest"); + EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url()); + + attempter_.CheckForInstall({}, "autest-scheduled"); + EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url()); + + attempter_.CheckForInstall({}, "http://omaha.phishing"); + EXPECT_EQ("", attempter_.forced_omaha_url()); +} + +TEST_F(UpdateAttempterTest, InstallSetsStatusIdle) { + attempter_.CheckForInstall({}, "http://foo.bar"); + attempter_.status_ = UpdateStatus::DOWNLOADING; + EXPECT_TRUE(attempter_.is_install_); + attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); + UpdateEngineStatus status; + attempter_.GetStatus(&status); + // Should set status to idle after an install operation. + EXPECT_EQ(UpdateStatus::IDLE, status.status); +} + +TEST_F(UpdateAttempterTest, RollbackAfterInstall) { + attempter_.is_install_ = true; + attempter_.Rollback(false); + EXPECT_FALSE(attempter_.is_install_); +} + +TEST_F(UpdateAttempterTest, UpdateAfterInstall) { + attempter_.is_install_ = true; + attempter_.CheckForUpdate("", "", UpdateAttemptFlags::kNone); + EXPECT_FALSE(attempter_.is_install_); +} + +TEST_F(UpdateAttempterTest, TargetVersionPrefixSetAndReset) { + UpdateCheckParams params; + attempter_.CalculateUpdateParams({.target_version_prefix = "1234"}); + EXPECT_EQ("1234", + FakeSystemState::Get()->request_params()->target_version_prefix()); + + attempter_.CalculateUpdateParams({}); + EXPECT_TRUE(FakeSystemState::Get() + ->request_params() + ->target_version_prefix() + .empty()); +} + +TEST_F(UpdateAttempterTest, TargetChannelHintSetAndReset) { + attempter_.CalculateUpdateParams({.lts_tag = "hint"}); + EXPECT_EQ("hint", FakeSystemState::Get()->request_params()->lts_tag()); + + attempter_.CalculateUpdateParams({}); + EXPECT_TRUE(FakeSystemState::Get()->request_params()->lts_tag().empty()); +} + +TEST_F(UpdateAttempterTest, RollbackAllowedSetAndReset) { + attempter_.CalculateUpdateParams({ + .target_version_prefix = "1234", + .rollback_allowed = true, + .rollback_allowed_milestones = 4, + }); + EXPECT_TRUE(FakeSystemState::Get()->request_params()->rollback_allowed()); + EXPECT_EQ( + 4, + FakeSystemState::Get()->request_params()->rollback_allowed_milestones()); + + attempter_.CalculateUpdateParams({ + .target_version_prefix = "1234", + .rollback_allowed_milestones = 4, + }); + EXPECT_FALSE(FakeSystemState::Get()->request_params()->rollback_allowed()); + EXPECT_EQ( + 4, + FakeSystemState::Get()->request_params()->rollback_allowed_milestones()); +} + +TEST_F(UpdateAttempterTest, ChannelDowngradeNoRollback) { + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + FakeSystemState::Get()->request_params()->set_root(tempdir.GetPath().value()); + attempter_.CalculateUpdateParams({ + .target_channel = "stable-channel", + }); + EXPECT_FALSE( + FakeSystemState::Get()->request_params()->is_powerwash_allowed()); +} + +TEST_F(UpdateAttempterTest, ChannelDowngradeRollback) { + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + FakeSystemState::Get()->request_params()->set_root(tempdir.GetPath().value()); + attempter_.CalculateUpdateParams({ + .rollback_on_channel_downgrade = true, + .target_channel = "stable-channel", + }); + EXPECT_TRUE(FakeSystemState::Get()->request_params()->is_powerwash_allowed()); +} + +TEST_F(UpdateAttempterTest, UpdateDeferredByPolicyTest) { + // Construct an OmahaResponseHandlerAction that has processed an InstallPlan, + // but the update is being deferred by the Policy. + OmahaResponseHandlerAction response_action; + response_action.install_plan_.version = "a.b.c.d"; + response_action.install_plan_.payloads.push_back( + {.size = 1234ULL, .type = InstallPayloadType::kFull}); + // Inform the UpdateAttempter that the OmahaResponseHandlerAction has + // completed, with the deferred-update error code. + attempter_.ActionCompleted( + nullptr, &response_action, ErrorCode::kOmahaUpdateDeferredPerPolicy); + { + UpdateEngineStatus status; + attempter_.GetStatus(&status); + EXPECT_EQ(UpdateStatus::UPDATE_AVAILABLE, status.status); + EXPECT_TRUE(attempter_.install_plan_); + EXPECT_EQ(attempter_.install_plan_->version, status.new_version); + EXPECT_EQ(attempter_.install_plan_->payloads[0].size, + status.new_size_bytes); + } + // An "error" event should have been created to tell Omaha that the update is + // being deferred. + EXPECT_TRUE(nullptr != attempter_.error_event_); + EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type); + EXPECT_EQ(OmahaEvent::kResultUpdateDeferred, attempter_.error_event_->result); + ErrorCode expected_code = static_cast<ErrorCode>( + static_cast<int>(ErrorCode::kOmahaUpdateDeferredPerPolicy) | + static_cast<int>(ErrorCode::kTestOmahaUrlFlag)); + EXPECT_EQ(expected_code, attempter_.error_event_->error_code); + // End the processing + attempter_.ProcessingDone(nullptr, ErrorCode::kOmahaUpdateDeferredPerPolicy); + // Validate the state of the attempter. + { + UpdateEngineStatus status; + attempter_.GetStatus(&status); + EXPECT_EQ(UpdateStatus::REPORTING_ERROR_EVENT, status.status); + EXPECT_EQ(response_action.install_plan_.version, status.new_version); + EXPECT_EQ(response_action.install_plan_.payloads[0].size, + status.new_size_bytes); + } +} + +TEST_F(UpdateAttempterTest, UpdateIsNotRunningWhenUpdateAvailable) { + // Default construction for |waiting_for_scheduled_check_| is false. + EXPECT_FALSE(attempter_.IsBusyOrUpdateScheduled()); + // Verify in-progress update with UPDATE_AVAILABLE is running + attempter_.status_ = UpdateStatus::UPDATE_AVAILABLE; + EXPECT_TRUE(attempter_.IsBusyOrUpdateScheduled()); +} + +TEST_F(UpdateAttempterTest, UpdateAttemptFlagsCachedAtUpdateStart) { + attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kFlagRestrictDownload); + + UpdateCheckParams params = {.updates_enabled = true}; + attempter_.OnUpdateScheduled(EvalStatus::kSucceeded, params); + + EXPECT_EQ(UpdateAttemptFlags::kFlagRestrictDownload, + attempter_.GetCurrentUpdateAttemptFlags()); +} + +TEST_F(UpdateAttempterTest, RollbackNotAllowed) { + UpdateCheckParams params = {.updates_enabled = true, + .rollback_allowed = false}; + attempter_.OnUpdateScheduled(EvalStatus::kSucceeded, params); + EXPECT_FALSE(FakeSystemState::Get()->request_params()->rollback_allowed()); +} + +TEST_F(UpdateAttempterTest, RollbackAllowed) { + UpdateCheckParams params = {.updates_enabled = true, + .rollback_allowed = true}; + attempter_.OnUpdateScheduled(EvalStatus::kSucceeded, params); + EXPECT_TRUE(FakeSystemState::Get()->request_params()->rollback_allowed()); +} + +TEST_F(UpdateAttempterTest, InteractiveUpdateUsesPassedRestrictions) { + attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kFlagRestrictDownload); + + attempter_.CheckForUpdate("", "", UpdateAttemptFlags::kNone); + EXPECT_EQ(UpdateAttemptFlags::kNone, + attempter_.GetCurrentUpdateAttemptFlags()); +} + +TEST_F(UpdateAttempterTest, NonInteractiveUpdateUsesSetRestrictions) { + attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kNone); + + // This tests that when CheckForUpdate() is called with the non-interactive + // flag set, that it doesn't change the current UpdateAttemptFlags. + attempter_.CheckForUpdate("", + "", + UpdateAttemptFlags::kFlagNonInteractive | + UpdateAttemptFlags::kFlagRestrictDownload); + EXPECT_EQ(UpdateAttemptFlags::kNone, + attempter_.GetCurrentUpdateAttemptFlags()); +} + +void UpdateAttempterTest::ResetRollbackHappenedStart(bool is_consumer, + bool is_policy_loaded, + bool expected_reset) { + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), + GetRollbackHappened()) + .WillRepeatedly(Return(true)); + auto mock_policy_provider = + std::make_unique<NiceMock<policy::MockPolicyProvider>>(); + EXPECT_CALL(*mock_policy_provider, IsConsumerDevice()) + .WillRepeatedly(Return(is_consumer)); + EXPECT_CALL(*mock_policy_provider, device_policy_is_loaded()) + .WillRepeatedly(Return(is_policy_loaded)); + const policy::MockDevicePolicy device_policy; + EXPECT_CALL(*mock_policy_provider, GetDevicePolicy()) + .WillRepeatedly(ReturnRef(device_policy)); + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), + SetRollbackHappened(false)) + .Times(expected_reset ? 1 : 0); + attempter_.policy_provider_ = std::move(mock_policy_provider); + attempter_.Update({}); + ScheduleQuitMainLoop(); +} + +TEST_F(UpdateAttempterTest, ResetRollbackHappenedOobe) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::ResetRollbackHappenedStart, + base::Unretained(this), + /*is_consumer=*/false, + /*is_policy_loaded=*/false, + /*expected_reset=*/false)); + loop_.Run(); +} + +TEST_F(UpdateAttempterTest, ResetRollbackHappenedConsumer) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::ResetRollbackHappenedStart, + base::Unretained(this), + /*is_consumer=*/true, + /*is_policy_loaded=*/false, + /*expected_reset=*/true)); + loop_.Run(); +} + +TEST_F(UpdateAttempterTest, ResetRollbackHappenedEnterprise) { + loop_.PostTask(FROM_HERE, + base::Bind(&UpdateAttempterTest::ResetRollbackHappenedStart, + base::Unretained(this), + /*is_consumer=*/false, + /*is_policy_loaded=*/true, + /*expected_reset=*/true)); + loop_.Run(); +} + +TEST_F(UpdateAttempterTest, SetRollbackHappenedRollback) { + attempter_.install_plan_.reset(new InstallPlan); + attempter_.install_plan_->is_rollback = true; + + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), + SetRollbackHappened(true)) + .Times(1); + attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); +} + +TEST_F(UpdateAttempterTest, SetRollbackHappenedNotRollback) { + attempter_.install_plan_.reset(new InstallPlan); + attempter_.install_plan_->is_rollback = false; + + EXPECT_CALL(*FakeSystemState::Get()->mock_payload_state(), + SetRollbackHappened(true)) + .Times(0); + attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); +} + +TEST_F(UpdateAttempterTest, RollbackMetricsRollbackSuccess) { + attempter_.install_plan_.reset(new InstallPlan); + attempter_.install_plan_->is_rollback = true; + attempter_.install_plan_->version = kRollbackVersion; + + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), + ReportEnterpriseRollbackMetrics(true, kRollbackVersion)) + .Times(1); + attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); +} + +TEST_F(UpdateAttempterTest, RollbackMetricsNotRollbackSuccess) { + attempter_.install_plan_.reset(new InstallPlan); + attempter_.install_plan_->is_rollback = false; + attempter_.install_plan_->version = kRollbackVersion; + + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), + ReportEnterpriseRollbackMetrics(_, _)) + .Times(0); + attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); +} + +TEST_F(UpdateAttempterTest, RollbackMetricsRollbackFailure) { + attempter_.install_plan_.reset(new InstallPlan); + attempter_.install_plan_->is_rollback = true; + attempter_.install_plan_->version = kRollbackVersion; + + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), + ReportEnterpriseRollbackMetrics(false, kRollbackVersion)) + .Times(1); + MockAction action; + attempter_.CreatePendingErrorEvent(&action, ErrorCode::kRollbackNotPossible); + attempter_.ProcessingDone(nullptr, ErrorCode::kRollbackNotPossible); +} + +TEST_F(UpdateAttempterTest, RollbackMetricsNotRollbackFailure) { + attempter_.install_plan_.reset(new InstallPlan); + attempter_.install_plan_->is_rollback = false; + attempter_.install_plan_->version = kRollbackVersion; + + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), + ReportEnterpriseRollbackMetrics(_, _)) + .Times(0); + MockAction action; + attempter_.CreatePendingErrorEvent(&action, ErrorCode::kRollbackNotPossible); + attempter_.ProcessingDone(nullptr, ErrorCode::kRollbackNotPossible); +} + +TEST_F(UpdateAttempterTest, TimeToUpdateAppliedMetricFailure) { + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), + ReportEnterpriseUpdateSeenToDownloadDays(_, _)) + .Times(0); + attempter_.ProcessingDone(nullptr, ErrorCode::kOmahaUpdateDeferredPerPolicy); +} + +TEST_F(UpdateAttempterTest, TimeToUpdateAppliedOnNonEnterprise) { + auto device_policy = std::make_unique<policy::MockDevicePolicy>(); + FakeSystemState::Get()->set_device_policy(device_policy.get()); + // Make device policy return that this is not enterprise enrolled + EXPECT_CALL(*device_policy, IsEnterpriseEnrolled()).WillOnce(Return(false)); + + // Ensure that the metric is not recorded. + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), + ReportEnterpriseUpdateSeenToDownloadDays(_, _)) + .Times(0); + attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); +} + +TEST_F(UpdateAttempterTest, + TimeToUpdateAppliedWithTimeRestrictionMetricSuccess) { + constexpr int kDaysToUpdate = 15; + auto device_policy = std::make_unique<policy::MockDevicePolicy>(); + FakeSystemState::Get()->set_device_policy(device_policy.get()); + // Make device policy return that this is enterprise enrolled + EXPECT_CALL(*device_policy, IsEnterpriseEnrolled()).WillOnce(Return(true)); + // Pretend that there's a time restriction policy in place + EXPECT_CALL(*device_policy, GetDisallowedTimeIntervals(_)) + .WillOnce(Return(true)); + + Time update_first_seen_at = Time::Now(); + FakeSystemState::Get()->fake_prefs()->SetInt64( + kPrefsUpdateFirstSeenAt, update_first_seen_at.ToInternalValue()); + + Time update_finished_at = + update_first_seen_at + TimeDelta::FromDays(kDaysToUpdate); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(update_finished_at); + + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), + ReportEnterpriseUpdateSeenToDownloadDays(true, kDaysToUpdate)) + .Times(1); + attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); +} + +TEST_F(UpdateAttempterTest, + TimeToUpdateAppliedWithoutTimeRestrictionMetricSuccess) { + constexpr int kDaysToUpdate = 15; + auto device_policy = std::make_unique<policy::MockDevicePolicy>(); + FakeSystemState::Get()->set_device_policy(device_policy.get()); + // Make device policy return that this is enterprise enrolled + EXPECT_CALL(*device_policy, IsEnterpriseEnrolled()).WillOnce(Return(true)); + // Pretend that there's no time restriction policy in place + EXPECT_CALL(*device_policy, GetDisallowedTimeIntervals(_)) + .WillOnce(Return(false)); + + Time update_first_seen_at = Time::Now(); + FakeSystemState::Get()->fake_prefs()->SetInt64( + kPrefsUpdateFirstSeenAt, update_first_seen_at.ToInternalValue()); + + Time update_finished_at = + update_first_seen_at + TimeDelta::FromDays(kDaysToUpdate); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(update_finished_at); + + EXPECT_CALL(*FakeSystemState::Get()->mock_metrics_reporter(), + ReportEnterpriseUpdateSeenToDownloadDays(false, kDaysToUpdate)) + .Times(1); + attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); +} + +TEST_F(UpdateAttempterTest, ProcessingDoneUpdated) { + // GIVEN an update finished. + + // THEN update_engine should call update completion. + pd_params_.should_update_completed_be_called = true; + // THEN need reboot since update applied. + pd_params_.expected_exit_status = UpdateStatus::UPDATED_NEED_REBOOT; + // THEN install indication should be false. + + TestProcessingDone(); +} + +TEST_F(UpdateAttempterTest, ProcessingDoneUpdatedDlcFilter) { + // GIVEN an update finished. + // GIVEN DLC |AppParams| list. + auto dlc_1 = "dlc_1", dlc_2 = "dlc_2"; + pd_params_.dlc_apps_params = {{dlc_1, {.name = dlc_1, .updated = false}}, + {dlc_2, {.name = dlc_2}}}; + + // THEN update_engine should call update completion. + pd_params_.should_update_completed_be_called = true; + pd_params_.args_to_update_completed = {dlc_2}; + // THEN need reboot since update applied. + pd_params_.expected_exit_status = UpdateStatus::UPDATED_NEED_REBOOT; + // THEN install indication should be false. + + TestProcessingDone(); +} + +TEST_F(UpdateAttempterTest, ProcessingDoneInstalled) { + // GIVEN an install finished. + pd_params_.is_install = true; + + // THEN update_engine should call install completion. + pd_params_.should_install_completed_be_called = true; + // THEN go idle. + // THEN install indication should be false. + + TestProcessingDone(); +} + +TEST_F(UpdateAttempterTest, ProcessingDoneInstalledDlcFilter) { + // GIVEN an install finished. + pd_params_.is_install = true; + // GIVEN DLC |AppParams| list. + auto dlc_1 = "dlc_1", dlc_2 = "dlc_2"; + pd_params_.dlc_apps_params = {{dlc_1, {.name = dlc_1, .updated = false}}, + {dlc_2, {.name = dlc_2}}}; + + // THEN update_engine should call install completion. + pd_params_.should_install_completed_be_called = true; + pd_params_.args_to_install_completed = {dlc_2}; + // THEN go idle. + // THEN install indication should be false. + + TestProcessingDone(); +} + +TEST_F(UpdateAttempterTest, ProcessingDoneInstallReportingError) { + // GIVEN an install finished. + pd_params_.is_install = true; + // GIVEN a reporting error occurred. + pd_params_.status = UpdateStatus::REPORTING_ERROR_EVENT; + + // THEN update_engine should not call install completion. + // THEN go idle. + // THEN install indication should be false. + + TestProcessingDone(); +} + +TEST_F(UpdateAttempterTest, ProcessingDoneNoUpdate) { + // GIVEN an update finished. + // GIVEN an action error occured. + pd_params_.code = ErrorCode::kNoUpdate; + + // THEN update_engine should not call update completion. + // THEN go idle. + // THEN install indication should be false. + + TestProcessingDone(); +} + +TEST_F(UpdateAttempterTest, ProcessingDoneNoInstall) { + // GIVEN an install finished. + pd_params_.is_install = true; + // GIVEN an action error occured. + pd_params_.code = ErrorCode::kNoUpdate; + + // THEN update_engine should not call install completion. + // THEN go idle. + // THEN install indication should be false. + + TestProcessingDone(); +} + +TEST_F(UpdateAttempterTest, ProcessingDoneUpdateError) { + // GIVEN an update finished. + // GIVEN an action error occured. + pd_params_.code = ErrorCode::kError; + // GIVEN an event error is set. + attempter_.error_event_.reset(new OmahaEvent(OmahaEvent::kTypeUpdateComplete, + OmahaEvent::kResultError, + ErrorCode::kError)); + + // THEN indicate a error event. + pd_params_.expected_exit_status = UpdateStatus::REPORTING_ERROR_EVENT; + // THEN install indication should be false. + + // THEN update_engine should not call update completion. + // THEN expect critical actions of |ScheduleErrorEventAction()|. + EXPECT_CALL(*processor_, EnqueueAction(Pointee(_))).Times(1); + EXPECT_CALL(*processor_, StartProcessing()).Times(1); + // THEN |ScheduleUpdates()| will be called next |ProcessingDone()| so skip. + pd_params_.should_schedule_updates_be_called = false; + + TestProcessingDone(); +} + +TEST_F(UpdateAttempterTest, ProcessingDoneInstallError) { + // GIVEN an install finished. + pd_params_.is_install = true; + // GIVEN an action error occured. + pd_params_.code = ErrorCode::kError; + // GIVEN an event error is set. + attempter_.error_event_.reset(new OmahaEvent(OmahaEvent::kTypeUpdateComplete, + OmahaEvent::kResultError, + ErrorCode::kError)); + + // THEN indicate a error event. + pd_params_.expected_exit_status = UpdateStatus::REPORTING_ERROR_EVENT; + // THEN install indication should be false. + + // THEN update_engine should not call install completion. + // THEN expect critical actions of |ScheduleErrorEventAction()|. + EXPECT_CALL(*processor_, EnqueueAction(Pointee(_))).Times(1); + EXPECT_CALL(*processor_, StartProcessing()).Times(1); + // THEN |ScheduleUpdates()| will be called next |ProcessingDone()| so skip. + pd_params_.should_schedule_updates_be_called = false; + + TestProcessingDone(); +} + +TEST_F(UpdateAttempterTest, QuickFixTokenWhenDeviceIsEnterpriseEnrolled) { + attempter_.CalculateUpdateParams({.quick_fix_build_token = "token"}); + EXPECT_EQ("token", + FakeSystemState::Get()->request_params()->autoupdate_token()); + + attempter_.CalculateUpdateParams({}); + EXPECT_TRUE( + FakeSystemState::Get()->request_params()->autoupdate_token().empty()); +} + +TEST_F(UpdateAttempterTest, ScheduleUpdateSpamHandlerTest) { + EXPECT_CALL(mock_update_manager_, AsyncPolicyRequestUpdateCheckAllowed(_, _)) + .Times(1); + EXPECT_TRUE(attempter_.ScheduleUpdates()); + // Now there is an update scheduled which means that all subsequent + // |ScheduleUpdates()| should fail. + EXPECT_FALSE(attempter_.ScheduleUpdates()); + EXPECT_FALSE(attempter_.ScheduleUpdates()); + EXPECT_FALSE(attempter_.ScheduleUpdates()); +} + +// Critical tests to always make sure that an update is scheduled. The following +// unittest(s) try and cover the correctness in synergy between +// |UpdateAttempter| and |UpdateManager|. Also it is good to remember the +// actions that happen in the flow when |UpdateAttempter| get callbacked on +// |OnUpdateScheduled()| -> (various cases which leads to) -> |ProcessingDone()| +void UpdateAttempterTest::TestOnUpdateScheduled() { + // Setup + attempter_.SetWaitingForScheduledCheck(true); + attempter_.DisableUpdate(); + attempter_.DisableScheduleUpdates(); + + // Invocation + attempter_.OnUpdateScheduled(ous_params_.status, ous_params_.params); + + // Verify + EXPECT_EQ(ous_params_.exit_status, attempter_.status()); + EXPECT_EQ(ous_params_.should_schedule_updates_be_called, + attempter_.WasScheduleUpdatesCalled()); + EXPECT_EQ(ous_params_.should_update_be_called, attempter_.WasUpdateCalled()); +} + +TEST_F(UpdateAttempterTest, OnUpdatesScheduledFailed) { + // GIVEN failed status. + + // THEN update should be scheduled. + ous_params_.should_schedule_updates_be_called = true; + + TestOnUpdateScheduled(); +} + +TEST_F(UpdateAttempterTest, OnUpdatesScheduledAskMeAgainLater) { + // GIVEN ask me again later status. + ous_params_.status = EvalStatus::kAskMeAgainLater; + + // THEN update should be scheduled. + ous_params_.should_schedule_updates_be_called = true; + + TestOnUpdateScheduled(); +} + +TEST_F(UpdateAttempterTest, OnUpdatesScheduledContinue) { + // GIVEN continue status. + ous_params_.status = EvalStatus::kContinue; + + // THEN update should be scheduled. + ous_params_.should_schedule_updates_be_called = true; + + TestOnUpdateScheduled(); +} + +TEST_F(UpdateAttempterTest, OnUpdatesScheduledSucceededButUpdateDisabledFails) { + // GIVEN updates disabled. + ous_params_.params = {.updates_enabled = false}; + // GIVEN succeeded status. + ous_params_.status = EvalStatus::kSucceeded; + + // THEN update should not be scheduled. + + TestOnUpdateScheduled(); +} + +TEST_F(UpdateAttempterTest, OnUpdatesScheduledSucceeded) { + // GIVEN updates enabled. + ous_params_.params = {.updates_enabled = true}; + // GIVEN succeeded status. + ous_params_.status = EvalStatus::kSucceeded; + + // THEN update should be called indicating status change. + ous_params_.exit_status = UpdateStatus::CHECKING_FOR_UPDATE; + ous_params_.should_update_be_called = true; + + TestOnUpdateScheduled(); +} + +TEST_F(UpdateAttempterTest, IsEnterpriseRollbackInGetStatusDefault) { + UpdateEngineStatus status; + attempter_.GetStatus(&status); + EXPECT_FALSE(status.is_enterprise_rollback); +} + +TEST_F(UpdateAttempterTest, IsEnterpriseRollbackInGetStatusFalse) { + attempter_.install_plan_.reset(new InstallPlan); + attempter_.install_plan_->is_rollback = false; + + UpdateEngineStatus status; + attempter_.GetStatus(&status); + EXPECT_FALSE(status.is_enterprise_rollback); +} + +TEST_F(UpdateAttempterTest, IsEnterpriseRollbackInGetStatusTrue) { + attempter_.install_plan_.reset(new InstallPlan); + attempter_.install_plan_->is_rollback = true; + + UpdateEngineStatus status; + attempter_.GetStatus(&status); + EXPECT_TRUE(status.is_enterprise_rollback); +} + +TEST_F(UpdateAttempterTest, PowerwashInGetStatusDefault) { + UpdateEngineStatus status; + attempter_.GetStatus(&status); + EXPECT_FALSE(status.will_powerwash_after_reboot); +} + +TEST_F(UpdateAttempterTest, PowerwashInGetStatusTrueBecausePowerwashRequired) { + attempter_.install_plan_.reset(new InstallPlan); + attempter_.install_plan_->powerwash_required = true; + + UpdateEngineStatus status; + attempter_.GetStatus(&status); + EXPECT_TRUE(status.will_powerwash_after_reboot); +} + +TEST_F(UpdateAttempterTest, PowerwashInGetStatusTrueBecauseRollback) { + attempter_.install_plan_.reset(new InstallPlan); + attempter_.install_plan_->is_rollback = true; + + UpdateEngineStatus status; + attempter_.GetStatus(&status); + EXPECT_TRUE(status.will_powerwash_after_reboot); +} + +TEST_F(UpdateAttempterTest, FutureEolTest) { + EolDate eol_date = std::numeric_limits<int64_t>::max(); + EXPECT_TRUE(prefs_->SetString(kPrefsOmahaEolDate, EolDateToString(eol_date))); + UpdateEngineStatus status; + attempter_.GetStatus(&status); + EXPECT_EQ(eol_date, status.eol_date); +} + +TEST_F(UpdateAttempterTest, PastEolTest) { + EolDate eol_date = 1; + EXPECT_TRUE(prefs_->SetString(kPrefsOmahaEolDate, EolDateToString(eol_date))); + UpdateEngineStatus status; + attempter_.GetStatus(&status); + EXPECT_EQ(eol_date, status.eol_date); +} + +TEST_F(UpdateAttempterTest, MissingEolTest) { + UpdateEngineStatus status; + attempter_.GetStatus(&status); + EXPECT_EQ(kEolDateInvalid, status.eol_date); +} + +TEST_F(UpdateAttempterTest, CalculateDlcParamsInstallTest) { + string dlc_id = "dlc0"; + attempter_.is_install_ = true; + attempter_.dlc_ids_ = {dlc_id}; + attempter_.CalculateDlcParams(); + + OmahaRequestParams* params = FakeSystemState::Get()->request_params(); + EXPECT_EQ(1, params->dlc_apps_params().count(params->GetDlcAppId(dlc_id))); + OmahaRequestParams::AppParams dlc_app_params = + params->dlc_apps_params().at(params->GetDlcAppId(dlc_id)); + EXPECT_STREQ(dlc_id.c_str(), dlc_app_params.name.c_str()); + EXPECT_EQ(false, dlc_app_params.send_ping); + // When the DLC gets installed, a ping is not sent, therefore we don't store + // the values sent by Omaha. + auto last_active_key = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, dlc_id, kPrefsPingLastActive}); + EXPECT_FALSE(FakeSystemState::Get()->prefs()->Exists(last_active_key)); + auto last_rollcall_key = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, dlc_id, kPrefsPingLastRollcall}); + EXPECT_FALSE(FakeSystemState::Get()->prefs()->Exists(last_rollcall_key)); +} + +TEST_F(UpdateAttempterTest, CalculateDlcParamsNoPrefFilesTest) { + string dlc_id = "dlc0"; + EXPECT_CALL(mock_dlcservice_, GetDlcsToUpdate(_)) + .WillOnce( + DoAll(SetArgPointee<0>(std::vector<string>({dlc_id})), Return(true))); + + attempter_.is_install_ = false; + attempter_.CalculateDlcParams(); + + OmahaRequestParams* params = FakeSystemState::Get()->request_params(); + EXPECT_EQ(1, params->dlc_apps_params().count(params->GetDlcAppId(dlc_id))); + OmahaRequestParams::AppParams dlc_app_params = + params->dlc_apps_params().at(params->GetDlcAppId(dlc_id)); + EXPECT_STREQ(dlc_id.c_str(), dlc_app_params.name.c_str()); + + EXPECT_EQ(true, dlc_app_params.send_ping); + EXPECT_EQ(0, dlc_app_params.ping_active); + EXPECT_EQ(-1, dlc_app_params.ping_date_last_active); + EXPECT_EQ(-1, dlc_app_params.ping_date_last_rollcall); +} + +TEST_F(UpdateAttempterTest, CalculateDlcParamsNonParseableValuesTest) { + string dlc_id = "dlc0"; + MemoryPrefs prefs; + FakeSystemState::Get()->set_prefs(&prefs); + EXPECT_CALL(mock_dlcservice_, GetDlcsToUpdate(_)) + .WillOnce( + DoAll(SetArgPointee<0>(std::vector<string>({dlc_id})), Return(true))); + + // Write non numeric values in the metadata files. + auto active_key = + PrefsInterface::CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive}); + auto last_active_key = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, dlc_id, kPrefsPingLastActive}); + auto last_rollcall_key = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, dlc_id, kPrefsPingLastRollcall}); + FakeSystemState::Get()->prefs()->SetString(active_key, "z2yz"); + FakeSystemState::Get()->prefs()->SetString(last_active_key, "z2yz"); + FakeSystemState::Get()->prefs()->SetString(last_rollcall_key, "z2yz"); + attempter_.is_install_ = false; + attempter_.CalculateDlcParams(); + + OmahaRequestParams* params = FakeSystemState::Get()->request_params(); + EXPECT_EQ(1, params->dlc_apps_params().count(params->GetDlcAppId(dlc_id))); + OmahaRequestParams::AppParams dlc_app_params = + params->dlc_apps_params().at(params->GetDlcAppId(dlc_id)); + EXPECT_STREQ(dlc_id.c_str(), dlc_app_params.name.c_str()); + + EXPECT_EQ(true, dlc_app_params.send_ping); + EXPECT_EQ(0, dlc_app_params.ping_active); + EXPECT_EQ(-2, dlc_app_params.ping_date_last_active); + EXPECT_EQ(-2, dlc_app_params.ping_date_last_rollcall); +} + +TEST_F(UpdateAttempterTest, CalculateDlcParamsValidValuesTest) { + string dlc_id = "dlc0"; + EXPECT_CALL(mock_dlcservice_, GetDlcsToUpdate(_)) + .WillOnce( + DoAll(SetArgPointee<0>(std::vector<string>({dlc_id})), Return(true))); + + // Write numeric values in the metadata files. + auto active_key = + PrefsInterface::CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive}); + auto last_active_key = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, dlc_id, kPrefsPingLastActive}); + auto last_rollcall_key = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, dlc_id, kPrefsPingLastRollcall}); + + FakeSystemState::Get()->prefs()->SetInt64(active_key, 1); + FakeSystemState::Get()->prefs()->SetInt64(last_active_key, 78); + FakeSystemState::Get()->prefs()->SetInt64(last_rollcall_key, 99); + attempter_.is_install_ = false; + attempter_.CalculateDlcParams(); + + OmahaRequestParams* params = FakeSystemState::Get()->request_params(); + EXPECT_EQ(1, params->dlc_apps_params().count(params->GetDlcAppId(dlc_id))); + OmahaRequestParams::AppParams dlc_app_params = + params->dlc_apps_params().at(params->GetDlcAppId(dlc_id)); + EXPECT_STREQ(dlc_id.c_str(), dlc_app_params.name.c_str()); + + EXPECT_EQ(true, dlc_app_params.send_ping); + EXPECT_EQ(1, dlc_app_params.ping_active); + EXPECT_EQ(78, dlc_app_params.ping_date_last_active); + EXPECT_EQ(99, dlc_app_params.ping_date_last_rollcall); +} + +TEST_F(UpdateAttempterTest, CalculateDlcParamsRemoveStaleMetadata) { + string dlc_id = "dlc0"; + auto active_key = + PrefsInterface::CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive}); + auto last_active_key = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, dlc_id, kPrefsPingLastActive}); + auto last_rollcall_key = PrefsInterface::CreateSubKey( + {kDlcPrefsSubDir, dlc_id, kPrefsPingLastRollcall}); + FakeSystemState::Get()->prefs()->SetInt64(active_key, kPingInactiveValue); + FakeSystemState::Get()->prefs()->SetInt64(last_active_key, 0); + FakeSystemState::Get()->prefs()->SetInt64(last_rollcall_key, 0); + EXPECT_TRUE(FakeSystemState::Get()->prefs()->Exists(active_key)); + EXPECT_TRUE(FakeSystemState::Get()->prefs()->Exists(last_active_key)); + EXPECT_TRUE(FakeSystemState::Get()->prefs()->Exists(last_rollcall_key)); + + attempter_.dlc_ids_ = {dlc_id}; + attempter_.is_install_ = true; + attempter_.CalculateDlcParams(); + + EXPECT_FALSE(FakeSystemState::Get()->prefs()->Exists(last_active_key)); + EXPECT_FALSE(FakeSystemState::Get()->prefs()->Exists(last_rollcall_key)); + // Active key is set on install. + EXPECT_TRUE(FakeSystemState::Get()->prefs()->Exists(active_key)); + int64_t temp_int; + EXPECT_TRUE(FakeSystemState::Get()->prefs()->GetInt64(active_key, &temp_int)); + EXPECT_EQ(temp_int, kPingActiveValue); +} + +TEST_F(UpdateAttempterTest, SetDlcActiveValue) { + string dlc_id = "dlc0"; + attempter_.SetDlcActiveValue(true, dlc_id); + int64_t temp_int; + auto active_key = + PrefsInterface::CreateSubKey({kDlcPrefsSubDir, dlc_id, kPrefsPingActive}); + EXPECT_TRUE(FakeSystemState::Get()->prefs()->Exists(active_key)); + EXPECT_TRUE(FakeSystemState::Get()->prefs()->GetInt64(active_key, &temp_int)); + EXPECT_EQ(temp_int, kPingActiveValue); +} + +TEST_F(UpdateAttempterTest, SetDlcInactive) { + string dlc_id = "dlc0"; + auto sub_keys = { + kPrefsPingActive, kPrefsPingLastActive, kPrefsPingLastRollcall}; + for (auto& sub_key : sub_keys) { + auto key = PrefsInterface::CreateSubKey({kDlcPrefsSubDir, dlc_id, sub_key}); + FakeSystemState::Get()->prefs()->SetInt64(key, 1); + EXPECT_TRUE(FakeSystemState::Get()->prefs()->Exists(key)); + } + attempter_.SetDlcActiveValue(false, dlc_id); + for (auto& sub_key : sub_keys) { + auto key = PrefsInterface::CreateSubKey({kDlcPrefsSubDir, dlc_id, sub_key}); + EXPECT_FALSE(FakeSystemState::Get()->prefs()->Exists(key)); + } +} + +TEST_F(UpdateAttempterTest, GetSuccessfulDlcIds) { + auto dlc_1 = "1", dlc_2 = "2", dlc_3 = "3"; + attempter_.omaha_request_params_->set_dlc_apps_params( + {{dlc_1, {.name = dlc_1, .updated = false}}, + {dlc_2, {.name = dlc_2}}, + {dlc_3, {.name = dlc_3, .updated = false}}}); + EXPECT_THAT(attempter_.GetSuccessfulDlcIds(), ElementsAre(dlc_2)); +} + +TEST_F(UpdateAttempterTest, MoveToPrefs) { + string key1 = kPrefsLastActivePingDay; + string key2 = kPrefsPingLastRollcall; + + FakePrefs fake_prefs; + EXPECT_TRUE(fake_prefs.SetString(key2, "current-rollcall")); + FakeSystemState::Get()->set_prefs(&fake_prefs); + + FakePrefs powerwash_safe_prefs; + EXPECT_TRUE(powerwash_safe_prefs.SetString(key1, "powerwash-last-active")); + EXPECT_TRUE(powerwash_safe_prefs.SetString(key2, "powerwash-last-rollcall")); + FakeSystemState::Get()->set_powerwash_safe_prefs(&powerwash_safe_prefs); + + attempter_.Init(); + attempter_.MoveToPrefs({key1, key2}); + + string pref_value_1; + fake_prefs.GetString(key1, &pref_value_1); + EXPECT_EQ(pref_value_1, "powerwash-last-active"); + // Do not overwrite if value already exists. + string pref_value_2; + fake_prefs.GetString(key2, &pref_value_2); + EXPECT_EQ(pref_value_2, "current-rollcall"); + + // Make sure keys are deleted from powerwash safe prefs regardless of whether + // they are written to prefs. + EXPECT_FALSE(FakeSystemState::Get()->powerwash_safe_prefs()->Exists(key1)); + EXPECT_FALSE(FakeSystemState::Get()->powerwash_safe_prefs()->Exists(key2)); +} + +} // namespace chromeos_update_engine diff --git a/update_engine_client.cc b/cros/update_engine_client.cc index 7446041c..6f20f11a 100644 --- a/update_engine_client.cc +++ b/cros/update_engine_client.cc @@ -26,26 +26,33 @@ #include <base/command_line.h> #include <base/logging.h> #include <base/macros.h> +#include <base/strings/string_number_conversions.h> #include <base/strings/string_split.h> #include <base/threading/platform_thread.h> +#include <base/threading/thread_task_runner_handle.h> #include <brillo/daemons/daemon.h> #include <brillo/flag_helper.h> +#include <brillo/key_value_store.h> #include "update_engine/client.h" #include "update_engine/common/error_code.h" #include "update_engine/common/error_code_utils.h" -#include "update_engine/omaha_utils.h" +#include "update_engine/cros/omaha_utils.h" #include "update_engine/status_update_handler.h" #include "update_engine/update_status.h" #include "update_engine/update_status_utils.h" -using chromeos_update_engine::EolStatus; +using brillo::KeyValueStore; +using chromeos_update_engine::EolDate; +using chromeos_update_engine::EolDateToString; using chromeos_update_engine::ErrorCode; +using chromeos_update_engine::UpdateEngineStatusToString; using chromeos_update_engine::UpdateStatusToString; using chromeos_update_engine::utils::ErrorCodeToString; using std::string; using std::unique_ptr; using std::vector; +using update_engine::UpdateEngineStatus; using update_engine::UpdateStatus; namespace { @@ -80,7 +87,7 @@ class UpdateEngineClient : public brillo::Daemon { // We can't call QuitWithExitCode from OnInit(), so we delay the execution // of the ProcessFlags method after the Daemon initialization is done. - base::MessageLoop::current()->task_runner()->PostTask( + base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&UpdateEngineClient::ProcessFlagsAndExit, base::Unretained(this))); @@ -132,80 +139,47 @@ class WatchingStatusUpdateHandler : public ExitingStatusUpdateHandler { public: ~WatchingStatusUpdateHandler() override = default; - void HandleStatusUpdate(int64_t last_checked_time, - double progress, - UpdateStatus current_operation, - const string& new_version, - int64_t new_size) override; + void HandleStatusUpdate(const UpdateEngineStatus& status) override; }; void WatchingStatusUpdateHandler::HandleStatusUpdate( - int64_t last_checked_time, - double progress, - UpdateStatus current_operation, - const string& new_version, - int64_t new_size) { - LOG(INFO) << "Got status update:"; - LOG(INFO) << " last_checked_time: " << last_checked_time; - LOG(INFO) << " progress: " << progress; - LOG(INFO) << " current_operation: " - << UpdateStatusToString(current_operation); - LOG(INFO) << " new_version: " << new_version; - LOG(INFO) << " new_size: " << new_size; + const UpdateEngineStatus& status) { + LOG(INFO) << "Got status update: " << UpdateEngineStatusToString(status); } bool UpdateEngineClient::ShowStatus() { - int64_t last_checked_time = 0; - double progress = 0.0; - UpdateStatus current_op; - string new_version; - int64_t new_size = 0; - + UpdateEngineStatus status; int retry_count = kShowStatusRetryCount; while (retry_count > 0) { - if (client_->GetStatus(&last_checked_time, - &progress, - ¤t_op, - &new_version, - &new_size)) { + if (client_->GetStatus(&status)) { break; } if (--retry_count == 0) { return false; } - LOG(WARNING) << "Will try " << retry_count << " more times!"; + LOG(WARNING) + << "Failed to get the update_engine status. This can happen when the" + " update_engine is busy doing a heavy operation or if the" + " update-engine service is down. If it doesn't resolve, a restart of" + " the update-engine service is needed." + " Will try " + << retry_count << " more times!"; base::PlatformThread::Sleep( base::TimeDelta::FromSeconds(kShowStatusRetryIntervalInSeconds)); } - printf("LAST_CHECKED_TIME=%" PRIi64 - "\nPROGRESS=%f\nCURRENT_OP=%s\n" - "NEW_VERSION=%s\nNEW_SIZE=%" PRIi64 "\n", - last_checked_time, - progress, - UpdateStatusToString(current_op), - new_version.c_str(), - new_size); + printf("%s", UpdateEngineStatusToString(status).c_str()); return true; } int UpdateEngineClient::GetNeedReboot() { - int64_t last_checked_time = 0; - double progress = 0.0; - UpdateStatus current_op; - string new_version; - int64_t new_size = 0; - - if (!client_->GetStatus(&last_checked_time, - &progress, - ¤t_op, - &new_version, - &new_size)) { + UpdateEngineStatus status; + if (!client_->GetStatus(&status)) { return 1; } - if (current_op == UpdateStatus::UPDATED_NEED_REBOOT) { + if (status.status == UpdateStatus::UPDATED_NEED_REBOOT) { return 0; } @@ -220,35 +194,26 @@ class UpdateWaitHandler : public ExitingStatusUpdateHandler { ~UpdateWaitHandler() override = default; - void HandleStatusUpdate(int64_t last_checked_time, - double progress, - UpdateStatus current_operation, - const string& new_version, - int64_t new_size) override; + void HandleStatusUpdate(const UpdateEngineStatus& status) override; private: bool exit_on_error_; update_engine::UpdateEngineClient* client_; }; -void UpdateWaitHandler::HandleStatusUpdate(int64_t /* last_checked_time */, - double /* progress */, - UpdateStatus current_operation, - const string& /* new_version */, - int64_t /* new_size */) { - if (exit_on_error_ && current_operation == UpdateStatus::IDLE) { - int last_attempt_error; +void UpdateWaitHandler::HandleStatusUpdate(const UpdateEngineStatus& status) { + if (exit_on_error_ && status.status == UpdateStatus::IDLE) { + int last_attempt_error = static_cast<int>(ErrorCode::kSuccess); ErrorCode code = ErrorCode::kSuccess; if (client_ && client_->GetLastAttemptError(&last_attempt_error)) code = static_cast<ErrorCode>(last_attempt_error); LOG(ERROR) << "Update failed, current operation is " - << UpdateStatusToString(current_operation) - << ", last error code is " << ErrorCodeToString(code) << "(" - << last_attempt_error << ")"; + << UpdateStatusToString(status.status) << ", last error code is " + << ErrorCodeToString(code) << "(" << last_attempt_error << ")"; exit(1); } - if (current_operation == UpdateStatus::UPDATED_NEED_REBOOT) { + if (status.status == UpdateStatus::UPDATED_NEED_REBOOT) { LOG(INFO) << "Update succeeded -- reboot needed."; exit(0); } @@ -321,8 +286,6 @@ int UpdateEngineClient::ProcessFlags() { "Show the previous OS version used before the update reboot."); DEFINE_bool(last_attempt_error, false, "Show the last attempt error."); DEFINE_bool(eol_status, false, "Show the current end-of-life status."); - DEFINE_bool(install, false, "Requests an install."); - DEFINE_string(dlc_module_ids, "", "colon-separated list of DLC IDs."); // Boilerplate init commands. base::CommandLine::Init(argc_, argv_); @@ -507,30 +470,6 @@ int UpdateEngineClient::ProcessFlags() { } } - if (FLAGS_install) { - // Parse DLC module IDs. - vector<string> dlc_module_ids; - if (!FLAGS_dlc_module_ids.empty()) { - dlc_module_ids = base::SplitString(FLAGS_dlc_module_ids, - ":", - base::TRIM_WHITESPACE, - base::SPLIT_WANT_ALL); - } - if (dlc_module_ids.empty()) { - LOG(ERROR) << "dlc_module_ids is empty:" << FLAGS_dlc_module_ids; - return 1; - } - if (!client_->AttemptInstall(FLAGS_omaha_url, dlc_module_ids)) { - LOG(ERROR) << "AttemptInstall failed."; - return 1; - } - return 0; - } else if (!FLAGS_dlc_module_ids.empty()) { - LOG(ERROR) << "dlc_module_ids is not empty while install is not set:" - << FLAGS_dlc_module_ids; - return 1; - } - // Initiate an update check, if necessary. if (do_update_request) { LOG_IF(WARNING, FLAGS_reboot) << "-reboot flag ignored."; @@ -539,7 +478,7 @@ int UpdateEngineClient::ProcessFlags() { app_version = "ForcedUpdate"; LOG(INFO) << "Forcing an update by setting app_version to ForcedUpdate."; } - LOG(INFO) << "Initiating update check and install."; + LOG(INFO) << "Initiating update check."; if (!client_->AttemptUpdate( app_version, FLAGS_omaha_url, FLAGS_interactive)) { LOG(ERROR) << "Error checking for update."; @@ -622,21 +561,26 @@ int UpdateEngineClient::ProcessFlags() { LOG(ERROR) << "Error getting last attempt error."; } else { ErrorCode code = static_cast<ErrorCode>(last_attempt_error); - printf( - "ERROR_CODE=%i\n" - "ERROR_MESSAGE=%s\n", - last_attempt_error, - ErrorCodeToString(code).c_str()); + + KeyValueStore last_attempt_error_store; + last_attempt_error_store.SetString( + "ERROR_CODE", base::NumberToString(last_attempt_error)); + last_attempt_error_store.SetString("ERROR_MESSAGE", + ErrorCodeToString(code)); + printf("%s", last_attempt_error_store.SaveToString().c_str()); } } if (FLAGS_eol_status) { - int eol_status; - if (!client_->GetEolStatus(&eol_status)) { - LOG(ERROR) << "Error getting the end-of-life status."; + UpdateEngineStatus status; + if (!client_->GetStatus(&status)) { + LOG(ERROR) << "Error GetStatus() for getting EOL info."; } else { - EolStatus eol_status_code = static_cast<EolStatus>(eol_status); - printf("EOL_STATUS=%s\n", EolStatusToString(eol_status_code)); + EolDate eol_date_code = status.eol_date; + + KeyValueStore eol_status_store; + eol_status_store.SetString("EOL_DATE", EolDateToString(eol_date_code)); + printf("%s", eol_status_store.SaveToString().c_str()); } } diff --git a/daemon.cc b/daemon.cc deleted file mode 100644 index d42344aa..00000000 --- a/daemon.cc +++ /dev/null @@ -1,117 +0,0 @@ -// -// Copyright (C) 2015 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/daemon.h" - -#include <sysexits.h> - -#include <base/bind.h> -#include <base/location.h> -#if USE_BINDER -#include <binderwrapper/binder_wrapper.h> -#endif // USE_BINDER - -#if USE_OMAHA -#include "update_engine/real_system_state.h" -#else // !USE_OMAHA -#include "update_engine/daemon_state_android.h" -#endif // USE_OMAHA - -namespace chromeos_update_engine { - -int UpdateEngineDaemon::OnInit() { - // Register the |subprocess_| singleton with this Daemon as the signal - // handler. - subprocess_.Init(this); - - int exit_code = Daemon::OnInit(); - if (exit_code != EX_OK) - return exit_code; - -#if USE_BINDER - android::BinderWrapper::Create(); - binder_watcher_.Init(); -#endif // USE_BINDER - -#if USE_OMAHA - // Initialize update engine global state but continue if something fails. - // TODO(deymo): Move the daemon_state_ initialization to a factory method - // avoiding the explicit re-usage of the |bus| instance, shared between - // D-Bus service and D-Bus client calls. - RealSystemState* real_system_state = new RealSystemState(); - daemon_state_.reset(real_system_state); - LOG_IF(ERROR, !real_system_state->Initialize()) - << "Failed to initialize system state."; -#else // !USE_OMAHA - DaemonStateAndroid* daemon_state_android = new DaemonStateAndroid(); - daemon_state_.reset(daemon_state_android); - LOG_IF(ERROR, !daemon_state_android->Initialize()) - << "Failed to initialize system state."; -#endif // USE_OMAHA - -#if USE_BINDER - // Create the Binder Service. -#if USE_OMAHA - binder_service_ = new BinderUpdateEngineBrilloService{real_system_state}; -#else // !USE_OMAHA - binder_service_ = new BinderUpdateEngineAndroidService{ - daemon_state_android->service_delegate()}; -#endif // USE_OMAHA - 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()); -#endif // USE_BINDER - -#if USE_DBUS - // Create the DBus service. - dbus_adaptor_.reset(new UpdateEngineAdaptor(real_system_state)); - daemon_state_->AddObserver(dbus_adaptor_.get()); - - dbus_adaptor_->RegisterAsync(base::Bind(&UpdateEngineDaemon::OnDBusRegistered, - base::Unretained(this))); - LOG(INFO) << "Waiting for DBus object to be registered."; -#else // !USE_DBUS - daemon_state_->StartUpdater(); -#endif // USE_DBUS - return EX_OK; -} - -#if USE_DBUS -void UpdateEngineDaemon::OnDBusRegistered(bool succeeded) { - if (!succeeded) { - LOG(ERROR) << "Registering the UpdateEngineAdaptor"; - QuitWithExitCode(1); - return; - } - - // Take ownership of the service now that everything is initialized. We need - // to this now and not before to avoid exposing a well known DBus service - // path that doesn't have the service it is supposed to implement. - if (!dbus_adaptor_->RequestOwnership()) { - LOG(ERROR) << "Unable to take ownership of the DBus service, is there " - << "other update_engine daemon running?"; - QuitWithExitCode(1); - return; - } - daemon_state_->StartUpdater(); -} -#endif // USE_DBUS - -} // namespace chromeos_update_engine diff --git a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml index f81d4ed1..ac2f0211 100644 --- a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml +++ b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml @@ -1,4 +1,19 @@ <?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. +!--> <node name="/org/chromium/UpdateEngine"> <interface name="org.chromium.UpdateEngineInterface"> <annotation name="org.freedesktop.DBus.GLib.CSymbol" @@ -20,7 +35,12 @@ <arg type="i" name="flags" direction="in" /> </method> <method name="AttemptInstall"> - <arg type="s" name="dlc_request" direction="in" /> + <arg type="s" name="omaha_url" direction="in" /> + <arg type="as" name="dlc_ids" direction="in"> + <tp:docstring> + The list of DLC IDs that needs to be installed. + </tp:docstring> + </arg> </method> <method name="AttemptRollback"> <arg type="b" name="powerwash" direction="in" /> @@ -30,12 +50,26 @@ </method> <method name="ResetStatus"> </method> - <method name="GetStatus"> - <arg type="x" name="last_checked_time" direction="out" /> - <arg type="d" name="progress" direction="out" /> - <arg type="s" name="current_operation" direction="out" /> - <arg type="s" name="new_version" direction="out" /> - <arg type="x" name="new_size" direction="out" /> + <method name="SetDlcActiveValue"> + <arg type="b" name="is_active" direction="in"> + <tp:docstring> + If the DLC is being set to active or inactive. + </tp:docstring> + </arg> + <arg type="s" name="dlc_id" direction="in"> + <tp:docstring> + The ID of the DLC module that will be set to active/inactive. + </tp:docstring> + </arg> + </method> + <method name="GetStatusAdvanced"> + <arg type="ay" name="status" direction="out"> + <tp:docstring> + The current status serialized in a protobuf. + </tp:docstring> + <annotation name="org.chromium.DBus.Argument.ProtobufClass" + value="update_engine::StatusResult"/> + </arg> </method> <method name="RebootIfNeeded"> </method> @@ -80,12 +114,14 @@ <method name="GetDurationSinceUpdate"> <arg type="x" name="usec_wallclock" direction="out" /> </method> - <signal name="StatusUpdate"> - <arg type="x" name="last_checked_time" /> - <arg type="d" name="progress" /> - <arg type="s" name="current_operation" /> - <arg type="s" name="new_version" /> - <arg type="x" name="new_size" /> + <signal name="StatusUpdateAdvanced"> + <arg type="ay" name="status" direction="out"> + <tp:docstring> + The current status serialized in a protobuf. + </tp:docstring> + <annotation name="org.chromium.DBus.Argument.ProtobufClass" + value="update_engine::StatusResult"/> + </arg> </signal> <method name="GetPrevVersion"> <arg type="s" name="prev_version" direction="out" /> @@ -96,8 +132,5 @@ <method name="GetLastAttemptError"> <arg type="i" name="last_attempt_error" direction="out" /> </method> - <method name="GetEolStatus"> - <arg type="i" name="eol_status" direction="out" /> - </method> </interface> </node> diff --git a/dlcservice_chromeos.cc b/dlcservice_chromeos.cc deleted file mode 100644 index e95f08ff..00000000 --- a/dlcservice_chromeos.cc +++ /dev/null @@ -1,54 +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/dlcservice_chromeos.h" - -#include <dlcservice/dbus-proxies.h> -#include <dlcservice/proto_bindings/dlcservice.pb.h> - -#include "update_engine/dbus_connection.h" - -using std::string; -using std::vector; - -namespace chromeos_update_engine { - -std::unique_ptr<DlcServiceInterface> CreateDlcService() { - return std::make_unique<DlcServiceChromeOS>(); -} - -bool DlcServiceChromeOS::GetInstalled(vector<string>* dlc_module_ids) { - if (!dlc_module_ids) - return false; - org::chromium::DlcServiceInterfaceProxy dlcservice_proxy( - DBusConnection::Get()->GetDBus()); - string dlc_module_list_str; - if (!dlcservice_proxy.GetInstalled(&dlc_module_list_str, nullptr)) { - LOG(ERROR) << "dlcservice does not return installed DLC module list."; - return false; - } - dlcservice::DlcModuleList dlc_module_list; - if (!dlc_module_list.ParseFromString(dlc_module_list_str)) { - LOG(ERROR) << "Errors parsing DlcModuleList protobuf."; - return false; - } - for (const auto& dlc_module_info : dlc_module_list.dlc_module_infos()) { - dlc_module_ids->emplace_back(dlc_module_info.dlc_id()); - } - return true; -} - -} // namespace chromeos_update_engine diff --git a/download_action.cc b/download_action.cc new file mode 100644 index 00000000..62a84230 --- /dev/null +++ b/download_action.cc @@ -0,0 +1,281 @@ +// +// Copyright (C) 2011 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/common/download_action.h" + +#include <errno.h> + +#include <algorithm> +#include <string> + +#include <base/files/file_path.h> +#include <base/metrics/statistics_recorder.h> +#include <base/strings/stringprintf.h> + +#include "update_engine/common/action_pipe.h" +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/error_code_utils.h" +#include "update_engine/common/multi_range_http_fetcher.h" +#include "update_engine/common/prefs_interface.h" +#include "update_engine/common/utils.h" + +using base::FilePath; +using std::string; + +namespace chromeos_update_engine { + +DownloadAction::DownloadAction(PrefsInterface* prefs, + BootControlInterface* boot_control, + HardwareInterface* hardware, + HttpFetcher* http_fetcher, + bool interactive) + : prefs_(prefs), + boot_control_(boot_control), + hardware_(hardware), + http_fetcher_(new MultiRangeHttpFetcher(http_fetcher)), + interactive_(interactive), + code_(ErrorCode::kSuccess), + delegate_(nullptr) {} + +DownloadAction::~DownloadAction() {} + +void DownloadAction::PerformAction() { + http_fetcher_->set_delegate(this); + + // Get the InstallPlan and read it + CHECK(HasInputObject()); + install_plan_ = GetInputObject(); + install_plan_.Dump(); + + bytes_received_ = 0; + bytes_received_previous_payloads_ = 0; + bytes_total_ = 0; + for (const auto& payload : install_plan_.payloads) + bytes_total_ += payload.size; + + if (install_plan_.is_resume) { + int64_t payload_index = 0; + if (prefs_->GetInt64(kPrefsUpdateStatePayloadIndex, &payload_index) && + static_cast<size_t>(payload_index) < install_plan_.payloads.size()) { + // Save the index for the resume payload before downloading any previous + // payload, otherwise it will be overwritten. + resume_payload_index_ = payload_index; + for (int i = 0; i < payload_index; i++) + install_plan_.payloads[i].already_applied = true; + } + } + CHECK_GE(install_plan_.payloads.size(), 1UL); + if (!payload_) + payload_ = &install_plan_.payloads[0]; + + LOG(INFO) << "Marking new slot as unbootable"; + if (!boot_control_->MarkSlotUnbootable(install_plan_.target_slot)) { + LOG(WARNING) << "Unable to mark new slot " + << BootControlInterface::SlotName(install_plan_.target_slot) + << ". Proceeding with the update anyway."; + } + + 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 (delta_performer_ != nullptr) { + LOG(INFO) << "Using writer for test."; + } else { + delta_performer_.reset(new DeltaPerformer(prefs_, + boot_control_, + hardware_, + delegate_, + &install_plan_, + payload_, + interactive_)); + } + + if (install_plan_.is_resume && + payload_ == &install_plan_.payloads[resume_payload_index_]) { + // 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); + + // 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_); + } + 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. + int64_t next_data_offset = 0; + prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset); + uint64_t resume_offset = + manifest_metadata_size + manifest_signature_size + next_data_offset; + if (!payload_->size) { + http_fetcher_->AddRange(base_offset_ + resume_offset); + } else if (resume_offset < payload_->size) { + http_fetcher_->AddRange(base_offset_ + resume_offset, + payload_->size - resume_offset); + } + } else { + if (payload_->size) { + http_fetcher_->AddRange(base_offset_, payload_->size); + } else { + // If no payload size is passed we assume we read until the end of the + // stream. + http_fetcher_->AddRange(base_offset_); + } + } + + http_fetcher_->BeginTransfer(install_plan_.download_url); +} + +void DownloadAction::SuspendAction() { + http_fetcher_->Pause(); +} + +void DownloadAction::ResumeAction() { + http_fetcher_->Unpause(); +} + +void DownloadAction::TerminateProcessing() { + if (delta_performer_) { + delta_performer_->Close(); + delta_performer_.reset(); + } + download_active_ = false; + // Terminates the transfer. The action is terminated, if necessary, when the + // TransferTerminated callback is received. + http_fetcher_->TerminateTransfer(); +} + +void DownloadAction::SeekToOffset(off_t offset) { + bytes_received_ = offset; +} + +bool DownloadAction::ReceivedBytes(HttpFetcher* fetcher, + const void* bytes, + size_t length) { + bytes_received_ += length; + uint64_t bytes_downloaded_total = + bytes_received_previous_payloads_ + bytes_received_; + if (delegate_ && download_active_) { + delegate_->BytesReceived(length, bytes_downloaded_total, bytes_total_); + } + if (delta_performer_ && !delta_performer_->Write(bytes, length, &code_)) { + if (code_ != ErrorCode::kSuccess) { + LOG(ERROR) << "Error " << utils::ErrorCodeToString(code_) << " (" << code_ + << ") in DeltaPerformer's Write method when " + << "processing the received payload -- Terminating processing"; + } + // Don't tell the action processor that the action is complete until we get + // the TransferTerminated callback. Otherwise, this and the HTTP fetcher + // objects may get destroyed before all callbacks are complete. + TerminateProcessing(); + return false; + } + + return true; +} + +void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) { + if (delta_performer_) { + LOG_IF(WARNING, delta_performer_->Close() != 0) + << "Error closing the writer."; + } + download_active_ = false; + ErrorCode code = + successful ? ErrorCode::kSuccess : ErrorCode::kDownloadTransferError; + if (code == ErrorCode::kSuccess) { + if (delta_performer_ && !payload_->already_applied) + code = delta_performer_->VerifyPayload(payload_->hash, payload_->size); + if (code == ErrorCode::kSuccess) { + CHECK_EQ(install_plan_.payloads.size(), 1UL); + // All payloads have been applied and verified. + if (delegate_) + delegate_->DownloadComplete(); + + // Log UpdateEngine.DownloadAction.* histograms to help diagnose + // long-blocking operations. + std::string histogram_output; + base::StatisticsRecorder::WriteGraph("UpdateEngine.DownloadAction.", + &histogram_output); + LOG(INFO) << histogram_output; + } else { + LOG(ERROR) << "Download of " << install_plan_.download_url + << " failed due to payload verification error."; + } + } + + // Write the path to the output pipe if we're successful. + if (code == ErrorCode::kSuccess && HasOutputPipe()) + SetOutputObject(install_plan_); + processor_->ActionComplete(this, code); +} + +void DownloadAction::TransferTerminated(HttpFetcher* fetcher) { + if (code_ != ErrorCode::kSuccess) { + processor_->ActionComplete(this, code_); + } else if (payload_->already_applied) { + LOG(INFO) << "TransferTerminated with ErrorCode::kSuccess when the current " + "payload has already applied, treating as TransferComplete."; + TransferComplete(fetcher, true); + } +} + +} // namespace chromeos_update_engine diff --git a/download_action_android_unittest.cc b/download_action_android_unittest.cc new file mode 100644 index 00000000..fef2d24d --- /dev/null +++ b/download_action_android_unittest.cc @@ -0,0 +1,180 @@ +// +// 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 <unistd.h> +#include <cstdint> +#include <memory> + +#include <gmock/gmock.h> +#include <gmock/gmock-actions.h> +#include <gmock/gmock-function-mocker.h> +#include <gmock/gmock-spec-builders.h> +#include <gtest/gtest.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/download_action.h" +#include "update_engine/common/fake_hardware.h" +#include "update_engine/common/mock_action_processor.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/common/utils.h" +#include "update_engine/payload_consumer/install_plan.h" +#include "update_engine/payload_consumer/payload_constants.h" +#include "update_engine/payload_generator/annotated_operation.h" +#include "update_engine/payload_generator/payload_file.h" +#include "update_engine/payload_generator/payload_signer.h" + +namespace chromeos_update_engine { +using testing::_; +using testing::DoAll; +using testing::Return; +using testing::SetArgPointee; + +extern const char* kUnittestPrivateKeyPath; +extern const char* kUnittestPublicKeyPath; + +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, 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()); +} + +TEST_F(DownloadActionTest, CacheManifestValid) { + // Create a valid manifest + PayloadGenerationConfig config; + config.version.major = kMaxSupportedMajorPayloadVersion; + config.version.minor = kMaxSupportedMinorPayloadVersion; + + PayloadFile payload_file; + ASSERT_TRUE(payload_file.Init(config)); + PartitionConfig partition_config{"system"}; + ScopedTempFile partition_file("part-system-XXXXXX", true); + ftruncate(partition_file.fd(), 4096); + partition_config.size = 4096; + partition_config.path = partition_file.path(); + ASSERT_TRUE( + payload_file.AddPartition(partition_config, partition_config, {}, {}, 0)); + ScopedTempFile blob_file("Blob-XXXXXX"); + ScopedTempFile manifest_file("Manifest-XXXXXX"); + uint64_t metadata_size; + std::string private_key = + test_utils::GetBuildArtifactsPath(kUnittestPrivateKeyPath); + payload_file.WritePayload( + manifest_file.path(), blob_file.path(), private_key, &metadata_size); + uint64_t signature_blob_length = 0; + ASSERT_TRUE(PayloadSigner::SignatureBlobLength({private_key}, + &signature_blob_length)); + std::string data; + ASSERT_TRUE(utils::ReadFile(manifest_file.path(), &data)); + data.resize(metadata_size + signature_blob_length); + + // Setup the prefs so that manifest is cached + 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_blob_length), 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))); + EXPECT_CALL(prefs, GetInt64(kPrefsUpdateStateNextOperation, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(0), Return(true))); + EXPECT_CALL(prefs, GetInt64(kPrefsUpdateStatePayloadIndex, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(0), 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; + auto& install_part = install_plan.partitions.emplace_back(); + install_part.source_path = partition_file.path(); + install_part.target_path = partition_file.path(); + action_pipe->set_contents(install_plan); + + FakeHardware hardware; + // takes ownership of passed in HttpFetcher + auto download_action = std::make_unique<DownloadAction>( + &prefs, &boot_control, &hardware, http_fetcher, false /* interactive */); + + auto delta_performer = std::make_unique<DeltaPerformer>(&prefs, + &boot_control, + &hardware, + nullptr, + &install_plan, + &payload, + false); + delta_performer->set_public_key_path(kUnittestPublicKeyPath); + download_action->SetTestFileWriter(std::move(delta_performer)); + download_action->set_in_pipe(action_pipe); + MockActionProcessor mock_processor; + download_action->SetProcessor(&mock_processor); + download_action->PerformAction(); + + // Manifest is cached, so no data should be downloaded from http fetcher. + ASSERT_EQ(download_action->http_fetcher()->GetBytesDownloaded(), 0UL); +} +} // namespace chromeos_update_engine diff --git a/image_properties_android.cc b/image_properties_android.cc deleted file mode 100644 index 2d418b3d..00000000 --- a/image_properties_android.cc +++ /dev/null @@ -1,246 +0,0 @@ -// -// Copyright (C) 2015 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/image_properties.h" - -#include <fcntl.h> - -#include <string> - -#include <android-base/properties.h> -#include <base/logging.h> -#include <base/strings/string_util.h> -#include <bootloader_message/bootloader_message.h> -#include <brillo/osrelease_reader.h> -#include <brillo/strings/string_utils.h> - -#include "update_engine/common/boot_control_interface.h" -#include "update_engine/common/constants.h" -#include "update_engine/common/platform_constants.h" -#include "update_engine/common/prefs_interface.h" -#include "update_engine/common/utils.h" -#include "update_engine/system_state.h" - -using android::base::GetProperty; -using std::string; - -namespace chromeos_update_engine { - -namespace { - -// Build time properties name used in Android Things. -const char kProductId[] = "product_id"; -const char kProductVersion[] = "product_version"; -const char kSystemId[] = "system_id"; -const char kSystemVersion[] = "system_version"; - -// The path to the product_components file which stores the version of each -// components in OEM partition. -const char kProductComponentsPath[] = "/oem/os-release.d/product_components"; - -// Prefs used to store the powerwash settings. -const char kPrefsImgPropPowerwashAllowed[] = "img-prop-powerwash-allowed"; - -// System properties that identifies the "board". -const char kPropProductName[] = "ro.product.name"; -const char kPropBuildFingerprint[] = "ro.build.fingerprint"; -const char kPropBuildType[] = "ro.build.type"; - -// Default channel from factory.prop -const char kPropDefaultChannel[] = "ro.update.default_channel"; - -// A prefix added to the path, used for testing. -const char* root_prefix = nullptr; - -string GetStringWithDefault(const brillo::OsReleaseReader& osrelease, - const string& key, - const string& default_value) { - string result; - if (osrelease.GetString(key, &result)) - return result; - LOG(INFO) << "Cannot load ImageProperty " << key << ", using default value " - << default_value; - return default_value; -} - -// Open misc partition for read or write and output the fd in |out_fd|. -bool OpenMisc(bool write, int* out_fd) { - string misc_device; - int flags = write ? O_WRONLY | O_SYNC : O_RDONLY; - if (root_prefix) { - // Use a file for unittest and create one if doesn't exist. - misc_device = base::FilePath(root_prefix).Append("misc").value(); - if (write) - flags |= O_CREAT; - } else { - string err; - misc_device = get_bootloader_message_blk_device(&err); - if (misc_device.empty()) { - LOG(ERROR) << "Unable to get misc block device: " << err; - return false; - } - } - - int fd = HANDLE_EINTR(open(misc_device.c_str(), flags, 0600)); - if (fd < 0) { - PLOG(ERROR) << "Opening misc failed"; - return false; - } - *out_fd = fd; - return true; -} - -// The offset and size of the channel field in misc partition. -constexpr size_t kChannelOffset = - BOOTLOADER_MESSAGE_OFFSET_IN_MISC + - offsetof(bootloader_message_ab, update_channel); -constexpr size_t kChannelSize = sizeof(bootloader_message_ab::update_channel); - -// Read channel from misc partition to |out_channel|, return false if unable to -// read misc or no channel is set in misc. -bool ReadChannelFromMisc(string* out_channel) { - int fd; - TEST_AND_RETURN_FALSE(OpenMisc(false, &fd)); - ScopedFdCloser fd_closer(&fd); - char channel[kChannelSize] = {0}; - ssize_t bytes_read = 0; - if (!utils::PReadAll( - fd, channel, kChannelSize - 1, kChannelOffset, &bytes_read) || - bytes_read != kChannelSize - 1) { - PLOG(ERROR) << "Reading update channel from misc failed"; - return false; - } - if (channel[0] == '\0') { - LOG(INFO) << "No channel set in misc."; - return false; - } - if (!base::EndsWith(channel, "-channel", base::CompareCase::SENSITIVE)) { - LOG(ERROR) << "Channel " << channel << " doesn't end with -channel."; - return false; - } - out_channel->assign(channel); - return true; -} - -// Write |in_channel| to misc partition, return false if failed to write. -bool WriteChannelToMisc(const string& in_channel) { - int fd; - TEST_AND_RETURN_FALSE(OpenMisc(true, &fd)); - ScopedFdCloser fd_closer(&fd); - if (in_channel.size() >= kChannelSize) { - LOG(ERROR) << "Channel name is too long: " << in_channel - << ", the maximum length is " << kChannelSize - 1; - return false; - } - char channel[kChannelSize] = {0}; - memcpy(channel, in_channel.data(), in_channel.size()); - if (!utils::PWriteAll(fd, channel, kChannelSize, kChannelOffset)) { - PLOG(ERROR) << "Writing update channel to misc failed"; - return false; - } - return true; -} - -string GetTargetChannel() { - string channel; - if (!ReadChannelFromMisc(&channel)) - channel = GetProperty(kPropDefaultChannel, "stable-channel"); - return channel; -} -} // namespace - -namespace test { -void SetImagePropertiesRootPrefix(const char* test_root_prefix) { - root_prefix = test_root_prefix; -} -} // namespace test - -ImageProperties LoadImageProperties(SystemState* system_state) { - ImageProperties result; - - brillo::OsReleaseReader osrelease; - if (root_prefix) - osrelease.LoadTestingOnly(base::FilePath(root_prefix)); - else - osrelease.Load(); - result.product_id = - GetStringWithDefault(osrelease, kProductId, "invalid-product"); - result.system_id = GetStringWithDefault( - osrelease, kSystemId, "developer-boards:brillo-starter-board"); - // Update the system id to match the prefix of product id for testing. - string prefix, not_used, system_id; - if (brillo::string_utils::SplitAtFirst( - result.product_id, ":", &prefix, ¬_used, false) && - brillo::string_utils::SplitAtFirst( - result.system_id, ":", ¬_used, &system_id, false)) { - result.system_id = prefix + ":" + system_id; - } - result.canary_product_id = result.product_id; - result.version = GetStringWithDefault(osrelease, kProductVersion, "0.0.0.0"); - result.system_version = - GetStringWithDefault(osrelease, kSystemVersion, "0.0.0.0"); - // Can't read it with OsReleaseReader because it has multiple lines. - utils::ReadFile(kProductComponentsPath, &result.product_components); - - result.board = GetProperty(kPropProductName, "brillo"); - result.build_fingerprint = GetProperty(kPropBuildFingerprint, "none"); - result.build_type = GetProperty(kPropBuildType, ""); - - // Android doesn't have channel information in system image, we try to read - // the channel of current slot from prefs and then fallback to use the - // persisted target channel as current channel. - string current_channel_key = - kPrefsChannelOnSlotPrefix + - std::to_string(system_state->boot_control()->GetCurrentSlot()); - string current_channel; - if (!system_state->prefs()->Exists(current_channel_key) || - !system_state->prefs()->GetString(current_channel_key, ¤t_channel)) - current_channel = GetTargetChannel(); - result.current_channel = current_channel; - result.allow_arbitrary_channels = true; - - // Brillo only supports the official omaha URL. - result.omaha_url = constants::kOmahaDefaultProductionURL; - - return result; -} - -MutableImageProperties LoadMutableImageProperties(SystemState* system_state) { - MutableImageProperties result; - result.target_channel = GetTargetChannel(); - if (!system_state->prefs()->GetBoolean(kPrefsImgPropPowerwashAllowed, - &result.is_powerwash_allowed)) { - result.is_powerwash_allowed = false; - } - return result; -} - -bool StoreMutableImageProperties(SystemState* system_state, - const MutableImageProperties& properties) { - bool ret = true; - if (!WriteChannelToMisc(properties.target_channel)) - ret = false; - if (!system_state->prefs()->SetBoolean(kPrefsImgPropPowerwashAllowed, - properties.is_powerwash_allowed)) - ret = false; - return ret; -} - -void LogImageProperties() { - // TODO(*): Implement this. -} - -} // namespace chromeos_update_engine diff --git a/image_properties_android_unittest.cc b/image_properties_android_unittest.cc deleted file mode 100644 index 607284a0..00000000 --- a/image_properties_android_unittest.cc +++ /dev/null @@ -1,123 +0,0 @@ -// -// Copyright (C) 2017 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/image_properties.h" - -#include <string> - -#include <base/files/file_util.h> -#include <base/files/scoped_temp_dir.h> -#include <gtest/gtest.h> - -#include "update_engine/common/constants.h" -#include "update_engine/common/fake_prefs.h" -#include "update_engine/common/test_utils.h" -#include "update_engine/fake_system_state.h" - -using chromeos_update_engine::test_utils::WriteFileString; -using std::string; - -namespace chromeos_update_engine { - -class ImagePropertiesTest : public ::testing::Test { - protected: - void SetUp() override { - // Create a uniquely named test directory. - ASSERT_TRUE(tempdir_.CreateUniqueTempDir()); - osrelease_dir_ = tempdir_.GetPath().Append("etc/os-release.d"); - EXPECT_TRUE(base::CreateDirectory(osrelease_dir_)); - test::SetImagePropertiesRootPrefix(tempdir_.GetPath().value().c_str()); - } - - void WriteOsRelease(const string& key, const string& value) { - ASSERT_TRUE(WriteFileString(osrelease_dir_.Append(key).value(), value)); - } - - void WriteChannel(const string& channel) { - string misc(2080, '\0'); - misc += channel; - misc.resize(4096); - ASSERT_TRUE( - WriteFileString(tempdir_.GetPath().Append("misc").value(), misc)); - } - - FakeSystemState fake_system_state_; - - base::ScopedTempDir tempdir_; - base::FilePath osrelease_dir_; -}; - -TEST_F(ImagePropertiesTest, SimpleTest) { - WriteOsRelease("product_id", "abc"); - WriteOsRelease("system_id", "def"); - WriteOsRelease("product_version", "1.2.3.4"); - WriteOsRelease("system_version", "5.6.7.8"); - ImageProperties props = LoadImageProperties(&fake_system_state_); - EXPECT_EQ("abc", props.product_id); - EXPECT_EQ("def", props.system_id); - EXPECT_EQ("1.2.3.4", props.version); - EXPECT_EQ("5.6.7.8", props.system_version); - EXPECT_EQ("stable-channel", props.current_channel); - EXPECT_EQ(constants::kOmahaDefaultProductionURL, props.omaha_url); -} - -TEST_F(ImagePropertiesTest, IDPrefixTest) { - WriteOsRelease("product_id", "abc:def"); - WriteOsRelease("system_id", "foo:bar"); - ImageProperties props = LoadImageProperties(&fake_system_state_); - EXPECT_EQ("abc:def", props.product_id); - EXPECT_EQ("abc:bar", props.system_id); -} - -TEST_F(ImagePropertiesTest, IDInvalidPrefixTest) { - WriteOsRelease("product_id", "def"); - WriteOsRelease("system_id", "foo:bar"); - ImageProperties props = LoadImageProperties(&fake_system_state_); - EXPECT_EQ("def", props.product_id); - EXPECT_EQ("foo:bar", props.system_id); - - WriteOsRelease("product_id", "abc:def"); - WriteOsRelease("system_id", "bar"); - props = LoadImageProperties(&fake_system_state_); - EXPECT_EQ("abc:def", props.product_id); - EXPECT_EQ("bar", props.system_id); -} - -TEST_F(ImagePropertiesTest, LoadChannelTest) { - WriteChannel("unittest-channel"); - ImageProperties props = LoadImageProperties(&fake_system_state_); - EXPECT_EQ("unittest-channel", props.current_channel); -} - -TEST_F(ImagePropertiesTest, DefaultStableChannelTest) { - WriteChannel(""); - ImageProperties props = LoadImageProperties(&fake_system_state_); - EXPECT_EQ("stable-channel", props.current_channel); -} - -TEST_F(ImagePropertiesTest, StoreLoadMutableChannelTest) { - FakePrefs prefs; - fake_system_state_.set_prefs(&prefs); - WriteChannel("previous-channel"); - MutableImageProperties props; - props.target_channel = "new-channel"; - EXPECT_TRUE(StoreMutableImageProperties(&fake_system_state_, props)); - MutableImageProperties loaded_props = - LoadMutableImageProperties(&fake_system_state_); - EXPECT_EQ(props.target_channel, loaded_props.target_channel); -} - -} // namespace chromeos_update_engine diff --git a/init/update-engine.conf b/init/update-engine.conf index d3681dbc..36c89d79 100644 --- a/init/update-engine.conf +++ b/init/update-engine.conf @@ -25,6 +25,7 @@ stop on stopping system-services # The default is 10 failures every 5 seconds, but even if we crash early, it is # hard to catch that. So here we set the crash rate as 10 failures every 20 # seconds which will include the default and more. +respawn respawn limit 10 20 expect fork @@ -36,7 +37,17 @@ exec ionice -c3 update_engine # Put update_engine process in its own cgroup. # Default cpu.shares is 1024. post-start script - cgroup_dir="/sys/fs/cgroup/cpu/${UPSTART_JOB}" - mkdir -p "${cgroup_dir}" - echo $(status | cut -f 4 -d ' ') > "${cgroup_dir}/tasks" + pid=$(status | cut -f 4 -d ' ') + + cgroup_cpu_dir="/sys/fs/cgroup/cpu/${UPSTART_JOB}" + mkdir -p "${cgroup_cpu_dir}" + echo ${pid} > "${cgroup_cpu_dir}/tasks" + + # Assigns net_cls handle 1:1 to packets generated from update_engine. For + # routing and tagging purposes, that value will be redefined in + # patchpanel/routing_service.h . + cgroup_net_cls_dir="/sys/fs/cgroup/net_cls/${UPSTART_JOB}" + mkdir -p "${cgroup_net_cls_dir}" + echo ${pid} > "${cgroup_net_cls_dir}/tasks" + echo "0x10001" > "${cgroup_net_cls_dir}/net_cls.classid" end script diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc index ce3475d4..1599aac1 100644 --- a/libcurl_http_fetcher.cc +++ b/libcurl_http_fetcher.cc @@ -16,6 +16,8 @@ #include "update_engine/libcurl_http_fetcher.h" +#include <netinet/in.h> +#include <resolv.h> #include <sys/types.h> #include <unistd.h> @@ -26,8 +28,10 @@ #include <base/format_macros.h> #include <base/location.h> #include <base/logging.h> +#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> @@ -75,18 +79,11 @@ 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 < arraysize(fetcher->fd_task_maps_); ++t) { - const auto fd_task_pair = fetcher->fd_task_maps_[t].find(item); - if (fd_task_pair != fetcher->fd_task_maps_[t].end()) { - if (!MessageLoop::current()->CancelTask(fd_task_pair->second)) { - LOG(WARNING) << "Error canceling the watch task " - << fd_task_pair->second << " for " - << (t ? "writing" : "reading") << " the fd " << item; - } - fetcher->fd_task_maps_[t].erase(item); - } + for (size_t t = 0; t < base::size(fetcher->fd_controller_maps_); ++t) { + fetcher->fd_controller_maps_[t].erase(item); } // Documentation for this callback says to return 0 on success or 1 on error. @@ -269,11 +266,11 @@ void LibcurlHttpFetcher::ResumeTransfer(const string& url) { } else if (base::StartsWith( url_, "https://", base::CompareCase::INSENSITIVE_ASCII)) { SetCurlOptionsForHttps(); -#if !USE_OMAHA +#ifdef __ANDROID__ } else if (base::StartsWith( url_, "file://", base::CompareCase::INSENSITIVE_ASCII)) { SetCurlOptionsForFile(); -#endif +#endif // __ANDROID__ } else { LOG(ERROR) << "Received invalid URI: " << url_; // Lock down to no protocol supported for the transfer. @@ -305,6 +302,7 @@ void LibcurlHttpFetcher::SetCurlOptionsForHttps() { LOG(INFO) << "Setting up curl options for HTTPS"; CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_VERIFYPEER, 1), CURLE_OK); CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_VERIFYHOST, 2), CURLE_OK); + CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_CAINFO, nullptr), CURLE_OK); CHECK_EQ(curl_easy_setopt( curl_handle_, CURLOPT_CAPATH, constants::kCACertificatesPath), CURLE_OK); @@ -391,6 +389,37 @@ void LibcurlHttpFetcher::SetHeader(const string& header_name, extra_headers_[base::ToLowerASCII(header_name)] = header_line; } +// Inputs: header_name, header_value +// Example: +// extra_headers_ = { {"foo":"foo: 123"}, {"bar":"bar:"} } +// string tmp = "gibberish"; +// Case 1: +// GetHeader("foo", &tmp) -> tmp = "123", return true. +// Case 2: +// GetHeader("bar", &tmp) -> tmp = "", return true. +// Case 3: +// GetHeader("moo", &tmp) -> tmp = "", return false. +bool LibcurlHttpFetcher::GetHeader(const string& header_name, + string* header_value) const { + // Initially clear |header_value| to handle both success and failures without + // leaving |header_value| in a unclear state. + header_value->clear(); + auto header_key = base::ToLowerASCII(header_name); + auto header_line_itr = extra_headers_.find(header_key); + // If the |header_name| was never set, indicate so by returning false. + if (header_line_itr == extra_headers_.end()) + return false; + // From |SetHeader()| the check for |header_name| to not include ":" is + // verified, so finding the first index of ":" is a safe operation. + auto header_line = header_line_itr->second; + *header_value = header_line.substr(header_line.find(':') + 1); + // The following is neccessary to remove the leading ' ' before the header + // value that was place only if |header_value| passed to |SetHeader()| was + // a non-empty string. + header_value->erase(0, 1); + return true; +} + void LibcurlHttpFetcher::CurlPerformOnce() { CHECK(transfer_in_progress_); int running_handles = 0; @@ -406,6 +435,18 @@ void LibcurlHttpFetcher::CurlPerformOnce() { } } + // When retcode is not |CURLM_OK| at this point, libcurl has an internal error + // that it is less likely to recover from (libcurl bug, out-of-memory, etc.). + // In case of an update check, we send UMA metrics and log the error. + if (is_update_check_ && + (retcode == CURLM_OUT_OF_MEMORY || retcode == CURLM_INTERNAL_ERROR)) { + auxiliary_error_code_ = ErrorCode::kInternalLibCurlError; + LOG(ERROR) << "curl_multi_perform is in an unrecoverable error condition: " + << retcode; + } else if (retcode != CURLM_OK) { + LOG(ERROR) << "curl_multi_perform returns error: " << retcode; + } + // If the transfer completes while paused, we should ignore the failure once // the fetcher is unpaused. if (running_handles == 0 && transfer_paused_ && !ignore_failure_) { @@ -417,6 +458,18 @@ 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. + // + // 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; + } SetupMessageLoopSources(); return; } @@ -428,13 +481,35 @@ void LibcurlHttpFetcher::CurlPerformOnce() { if (http_response_code_) { LOG(INFO) << "HTTP response code: " << http_response_code_; no_network_retry_count_ = 0; + unresolved_host_state_machine_.UpdateState(false); } else { LOG(ERROR) << "Unable to get http response code."; + CURLcode curl_code = GetCurlCode(); + LOG(ERROR) << "Return code for the transfer: " << curl_code; + if (curl_code == CURLE_COULDNT_RESOLVE_HOST) { + LOG(ERROR) << "libcurl can not resolve host."; + unresolved_host_state_machine_.UpdateState(true); + auxiliary_error_code_ = ErrorCode::kUnresolvedHostError; + } } // we're done! CleanUp(); + if (unresolved_host_state_machine_.GetState() == + UnresolvedHostStateMachine::State::kRetry) { + // Based on + // https://curl.haxx.se/docs/todo.html#updated_DNS_server_while_running, + // update_engine process should call res_init() and unconditionally retry. + res_init(); + no_network_max_retries_++; + LOG(INFO) << "Will retry after reloading resolv.conf because last attempt " + "failed to resolve host."; + } else if (unresolved_host_state_machine_.GetState() == + UnresolvedHostStateMachine::State::kRetriedSuccess) { + auxiliary_error_code_ = ErrorCode::kUnresolvedHostRecovered; + } + // TODO(petkov): This temporary code tries to deal with the case where the // update engine performs an update check while the network is not ready // (e.g., right after resume). Longer term, we should check if the network @@ -615,15 +690,15 @@ void LibcurlHttpFetcher::SetupMessageLoopSources() { // We should iterate through all file descriptors up to libcurl's fd_max or // the highest one we're tracking, whichever is larger. - for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) { - if (!fd_task_maps_[t].empty()) - fd_max = max(fd_max, fd_task_maps_[t].rbegin()->first); + for (size_t t = 0; t < base::size(fd_controller_maps_); ++t) { + if (!fd_controller_maps_[t].empty()) + fd_max = max(fd_max, fd_controller_maps_[t].rbegin()->first); } // For each fd, if we're not tracking it, track it. If we are tracking it, but // libcurl doesn't care about it anymore, stop tracking it. After this loop, - // there should be exactly as many tasks scheduled in fd_task_maps_[0|1] as - // there are read/write fds that we're tracking. + // there should be exactly as many tasks scheduled in + // fd_controller_maps_[0|1] as there are read/write fds that we're tracking. for (int fd = 0; fd <= fd_max; ++fd) { // Note that fd_exc is unused in the current version of libcurl so is_exc // should always be false. @@ -632,21 +707,14 @@ void LibcurlHttpFetcher::SetupMessageLoopSources() { is_exc || (FD_ISSET(fd, &fd_read) != 0), // track 0 -- read is_exc || (FD_ISSET(fd, &fd_write) != 0) // track 1 -- write }; - MessageLoop::WatchMode watch_modes[2] = { - MessageLoop::WatchMode::kWatchRead, - MessageLoop::WatchMode::kWatchWrite, - }; - for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) { - auto fd_task_it = fd_task_maps_[t].find(fd); - bool tracked = fd_task_it != fd_task_maps_[t].end(); + for (size_t t = 0; t < base::size(fd_controller_maps_); ++t) { + bool tracked = + fd_controller_maps_[t].find(fd) != fd_controller_maps_[t].end(); if (!must_track[t]) { // If we have an outstanding io_channel, remove it. - if (tracked) { - MessageLoop::current()->CancelTask(fd_task_it->second); - fd_task_maps_[t].erase(fd_task_it); - } + fd_controller_maps_[t].erase(fd); continue; } @@ -655,14 +723,21 @@ void LibcurlHttpFetcher::SetupMessageLoopSources() { continue; // Track a new fd. - fd_task_maps_[t][fd] = MessageLoop::current()->WatchFileDescriptor( - FROM_HERE, - fd, - watch_modes[t], - true, // persistent - base::Bind(&LibcurlHttpFetcher::CurlPerformOnce, - base::Unretained(this))); - + switch (t) { + case 0: // Read + fd_controller_maps_[t][fd] = + base::FileDescriptorWatcher::WatchReadable( + fd, + base::BindRepeating(&LibcurlHttpFetcher::CurlPerformOnce, + base::Unretained(this))); + break; + case 1: // Write + fd_controller_maps_[t][fd] = + base::FileDescriptorWatcher::WatchWritable( + fd, + base::BindRepeating(&LibcurlHttpFetcher::CurlPerformOnce, + base::Unretained(this))); + } static int io_counter = 0; io_counter++; if (io_counter % 50 == 0) { @@ -714,15 +789,8 @@ void LibcurlHttpFetcher::CleanUp() { MessageLoop::current()->CancelTask(timeout_id_); timeout_id_ = MessageLoop::kTaskIdNull; - for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) { - for (const auto& fd_taks_pair : fd_task_maps_[t]) { - if (!MessageLoop::current()->CancelTask(fd_taks_pair.second)) { - LOG(WARNING) << "Error canceling the watch task " << fd_taks_pair.second - << " for " << (t ? "writing" : "reading") << " the fd " - << fd_taks_pair.first; - } - } - fd_task_maps_[t].clear(); + for (size_t t = 0; t < base::size(fd_controller_maps_); ++t) { + fd_controller_maps_[t].clear(); } if (curl_http_headers_) { @@ -755,6 +823,66 @@ void LibcurlHttpFetcher::GetHttpResponseCode() { CURLINFO_RESPONSE_CODE, &http_response_code) == CURLE_OK) { http_response_code_ = static_cast<int>(http_response_code); + } else { + LOG(ERROR) << "Unable to get http response code from curl_easy_getinfo"; + } +} + +CURLcode LibcurlHttpFetcher::GetCurlCode() { + CURLcode curl_code = CURLE_OK; + while (true) { + // Repeated calls to |curl_multi_info_read| will return a new struct each + // time, until a NULL is returned as a signal that there is no more to get + // at this point. + int msgs_in_queue; + CURLMsg* curl_msg = + curl_multi_info_read(curl_multi_handle_, &msgs_in_queue); + if (curl_msg == nullptr) + break; + // When |curl_msg| is |CURLMSG_DONE|, a transfer of an easy handle is done, + // and then data contains the return code for this transfer. + if (curl_msg->msg == CURLMSG_DONE) { + // Make sure |curl_multi_handle_| has one and only one easy handle + // |curl_handle_|. + CHECK_EQ(curl_handle_, curl_msg->easy_handle); + // Transfer return code reference: + // https://curl.haxx.se/libcurl/c/libcurl-errors.html + curl_code = curl_msg->data.result; + } + } + + // Gets connection error if exists. + long connect_error = 0; // NOLINT(runtime/int) - curl needs long. + CURLcode res = + curl_easy_getinfo(curl_handle_, CURLINFO_OS_ERRNO, &connect_error); + if (res == CURLE_OK && connect_error) { + LOG(ERROR) << "Connect error code from the OS: " << connect_error; + } + + return curl_code; +} + +void UnresolvedHostStateMachine::UpdateState(bool failed_to_resolve_host) { + switch (state_) { + case State::kInit: + if (failed_to_resolve_host) { + state_ = State::kRetry; + } + break; + case State::kRetry: + if (failed_to_resolve_host) { + state_ = State::kNotRetry; + } else { + state_ = State::kRetriedSuccess; + } + break; + case State::kNotRetry: + break; + case State::kRetriedSuccess: + break; + default: + NOTREACHED(); + break; } } diff --git a/libcurl_http_fetcher.h b/libcurl_http_fetcher.h index 25a2df3d..4e91b693 100644 --- a/libcurl_http_fetcher.h +++ b/libcurl_http_fetcher.h @@ -24,6 +24,7 @@ #include <curl/curl.h> +#include <base/files/file_descriptor_watcher_posix.h> #include <base/logging.h> #include <base/macros.h> #include <brillo/message_loops/message_loop.h> @@ -37,6 +38,48 @@ namespace chromeos_update_engine { +// |UnresolvedHostStateMachine| is a representation of internal state machine of +// |LibcurlHttpFetcher|. +class UnresolvedHostStateMachine { + public: + UnresolvedHostStateMachine() = default; + enum class State { + kInit = 0, + kRetry = 1, + kRetriedSuccess = 2, + kNotRetry = 3, + }; + + State GetState() { return state_; } + + // Updates the following internal state machine: + // + // |kInit| + // | + // | + // \/ + // (Try, host Unresolved) + // | + // | + // \/ + // |kRetry| --> (Retry, host resolved) + // | | + // | | + // \/ \/ + // (Retry, host Unresolved) |kRetriedSuccess| + // | + // | + // \/ + // |kNotRetry| + // + void UpdateState(bool failed_to_resolve_host); + + private: + State state_ = {State::kInit}; + + DISALLOW_COPY_AND_ASSIGN(UnresolvedHostStateMachine); +}; + class LibcurlHttpFetcher : public HttpFetcher { public: LibcurlHttpFetcher(ProxyResolver* proxy_resolver, @@ -61,6 +104,9 @@ class LibcurlHttpFetcher : public HttpFetcher { void SetHeader(const std::string& header_name, const std::string& header_value) override; + bool GetHeader(const std::string& header_name, + std::string* header_value) const override; + // Suspend the transfer by calling curl_easy_pause(CURLPAUSE_ALL). void Pause() override; @@ -85,6 +131,8 @@ class LibcurlHttpFetcher : public HttpFetcher { no_network_max_retries_ = retries; } + int get_no_network_max_retries() { return no_network_max_retries_; } + void set_server_to_check(ServerToCheck server_to_check) { server_to_check_ = server_to_check; } @@ -106,7 +154,13 @@ class LibcurlHttpFetcher : public HttpFetcher { max_retry_count_ = max_retry_count; } + void set_is_update_check(bool is_update_check) { + is_update_check_ = is_update_check; + } + private: + FRIEND_TEST(LibcurlHttpFetcherTest, HostResolvedTest); + // libcurl's CURLOPT_CLOSESOCKETFUNCTION callback function. Called when // closing a socket created with the CURLOPT_OPENSOCKETFUNCTION callback. static int LibcurlCloseSocketCallback(void* clientp, curl_socket_t item); @@ -116,7 +170,10 @@ class LibcurlHttpFetcher : public HttpFetcher { void ProxiesResolved(); // Asks libcurl for the http response code and stores it in the object. - void GetHttpResponseCode(); + virtual void GetHttpResponseCode(); + + // Returns the last |CURLcode|. + CURLcode GetCurlCode(); // Checks whether stored HTTP response is within the success range. inline bool IsHttpResponseSuccess() { @@ -161,7 +218,7 @@ class LibcurlHttpFetcher : public HttpFetcher { } // Cleans up the following if they are non-null: - // curl(m) handles, fd_task_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) @@ -198,7 +255,8 @@ class LibcurlHttpFetcher : public HttpFetcher { // the message loop. libcurl may open/close descriptors and switch their // directions so maintain two separate lists so that watch conditions can be // set appropriately. - std::map<int, brillo::MessageLoop::TaskId> fd_task_maps_[2]; + std::map<int, std::unique_ptr<base::FileDescriptorWatcher::Controller>> + fd_controller_maps_[2]; // The TaskId of the timer we're waiting on. kTaskIdNull if we are not waiting // on it. @@ -265,6 +323,12 @@ class LibcurlHttpFetcher : public HttpFetcher { // ServerToCheck::kNone. ServerToCheck server_to_check_{ServerToCheck::kNone}; + // True if this object is for update check. + bool is_update_check_{false}; + + // Internal state machine. + UnresolvedHostStateMachine unresolved_host_state_machine_; + int low_speed_limit_bps_{kDownloadLowSpeedLimitBps}; int low_speed_time_seconds_{kDownloadLowSpeedTimeSeconds}; int connect_timeout_seconds_{kDownloadConnectTimeoutSeconds}; diff --git a/libcurl_http_fetcher_unittest.cc b/libcurl_http_fetcher_unittest.cc new file mode 100644 index 00000000..5d675707 --- /dev/null +++ b/libcurl_http_fetcher_unittest.cc @@ -0,0 +1,205 @@ +// +// 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/libcurl_http_fetcher.h" + +#include <string> + +#include <brillo/message_loops/fake_message_loop.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "update_engine/common/fake_hardware.h" +#include "update_engine/common/mock_proxy_resolver.h" +#include "update_engine/mock_libcurl_http_fetcher.h" + +using std::string; + +namespace chromeos_update_engine { + +namespace { +constexpr char kHeaderName[] = "X-Goog-Test-Header"; +} + +class LibcurlHttpFetcherTest : public ::testing::Test { + protected: + void SetUp() override { + loop_.SetAsCurrent(); + fake_hardware_.SetIsOfficialBuild(true); + fake_hardware_.SetIsOOBEEnabled(false); + } + + brillo::FakeMessageLoop loop_{nullptr}; + FakeHardware fake_hardware_; + MockLibcurlHttpFetcher libcurl_fetcher_{nullptr, &fake_hardware_}; + UnresolvedHostStateMachine state_machine_; +}; + +TEST_F(LibcurlHttpFetcherTest, GetEmptyHeaderValueTest) { + const string header_value = ""; + string actual_header_value; + libcurl_fetcher_.SetHeader(kHeaderName, header_value); + EXPECT_TRUE(libcurl_fetcher_.GetHeader(kHeaderName, &actual_header_value)); + EXPECT_EQ("", actual_header_value); +} + +TEST_F(LibcurlHttpFetcherTest, GetHeaderTest) { + const string header_value = "This-is-value 123"; + string actual_header_value; + libcurl_fetcher_.SetHeader(kHeaderName, header_value); + EXPECT_TRUE(libcurl_fetcher_.GetHeader(kHeaderName, &actual_header_value)); + EXPECT_EQ(header_value, actual_header_value); +} + +TEST_F(LibcurlHttpFetcherTest, GetNonExistentHeaderValueTest) { + string actual_header_value; + // Skip |SetHeaader()| call. + EXPECT_FALSE(libcurl_fetcher_.GetHeader(kHeaderName, &actual_header_value)); + // Even after a failed |GetHeaderValue()|, enforce that the passed pointer to + // modifiable string was cleared to be empty. + EXPECT_EQ("", actual_header_value); +} + +TEST_F(LibcurlHttpFetcherTest, GetHeaderEdgeCaseTest) { + const string header_value = "\a\b\t\v\f\r\\ edge:-case: \a\b\t\v\f\r\\"; + string actual_header_value; + libcurl_fetcher_.SetHeader(kHeaderName, header_value); + EXPECT_TRUE(libcurl_fetcher_.GetHeader(kHeaderName, &actual_header_value)); + EXPECT_EQ(header_value, actual_header_value); +} + +TEST_F(LibcurlHttpFetcherTest, InvalidURLTest) { + int no_network_max_retries = 1; + libcurl_fetcher_.set_no_network_max_retries(no_network_max_retries); + + libcurl_fetcher_.BeginTransfer("not-a-URL"); + while (loop_.PendingTasks()) { + loop_.RunOnce(true); + } + + EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(), + no_network_max_retries); +} + +TEST_F(LibcurlHttpFetcherTest, CouldNotResolveHostTest) { + int no_network_max_retries = 1; + libcurl_fetcher_.set_no_network_max_retries(no_network_max_retries); + + libcurl_fetcher_.BeginTransfer("https://An-uNres0lvable-uRl.invalid"); + + // 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); + } + + EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(), + ErrorCode::kUnresolvedHostError); + + while (loop_.PendingTasks()) { + loop_.RunOnce(true); + } + // The auxilary error code should've have been changed. + EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(), + ErrorCode::kUnresolvedHostError); + + // If libcurl fails to resolve the name, we call res_init() to reload + // resolv.conf and retry exactly once more. See crbug.com/982813 for details. + EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(), + no_network_max_retries + 1); +} + +TEST_F(LibcurlHttpFetcherTest, HostResolvedTest) { + int no_network_max_retries = 2; + libcurl_fetcher_.set_no_network_max_retries(no_network_max_retries); + + // This test actually sends request to internet but according to + // https://tools.ietf.org/html/rfc2606#section-2, .invalid domain names are + // reserved and sure to be invalid. Ideally we should mock libcurl or + // reorganize LibcurlHttpFetcher so the part that sends request can be mocked + // easily. + // TODO(xiaochu) Refactor LibcurlHttpFetcher (and its relates) so it's + // easier to mock the part that depends on internet connectivity. + libcurl_fetcher_.BeginTransfer("https://An-uNres0lvable-uRl.invalid"); + + // 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); + } + + EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(), + ErrorCode::kUnresolvedHostError); + + // The second time, it will resolve, with error code 200 but we set the + // download size be smaller than the transfer size so it will retry again. + EXPECT_CALL(libcurl_fetcher_, GetHttpResponseCode()) + .WillOnce(testing::Invoke( + [this]() { libcurl_fetcher_.http_response_code_ = 200; })) + .WillRepeatedly(testing::Invoke( + [this]() { libcurl_fetcher_.http_response_code_ = 0; })); + libcurl_fetcher_.transfer_size_ = 10; + + // 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); + } + + EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(), + ErrorCode::kUnresolvedHostRecovered); + + while (loop_.PendingTasks()) { + loop_.RunOnce(true); + } + // The auxilary error code should not have been changed. + EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(), + ErrorCode::kUnresolvedHostRecovered); + + // If libcurl fails to resolve the name, we call res_init() to reload + // resolv.conf and retry exactly once more. See crbug.com/982813 for details. + EXPECT_EQ(libcurl_fetcher_.get_no_network_max_retries(), + no_network_max_retries + 1); +} + +TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineRetryFailedTest) { + state_machine_.UpdateState(true); + state_machine_.UpdateState(true); + EXPECT_EQ(state_machine_.GetState(), + UnresolvedHostStateMachine::State::kNotRetry); +} + +TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineRetrySucceedTest) { + state_machine_.UpdateState(true); + state_machine_.UpdateState(false); + EXPECT_EQ(state_machine_.GetState(), + UnresolvedHostStateMachine::State::kRetriedSuccess); +} + +TEST_F(LibcurlHttpFetcherTest, HttpFetcherStateMachineNoRetryTest) { + state_machine_.UpdateState(false); + state_machine_.UpdateState(false); + EXPECT_EQ(state_machine_.GetState(), + UnresolvedHostStateMachine::State::kInit); +} + +} // namespace chromeos_update_engine @@ -23,10 +23,11 @@ #include <base/logging.h> #include <brillo/flag_helper.h> +#include "update_engine/common/daemon_base.h" +#include "update_engine/common/logging.h" +#include "update_engine/common/subprocess.h" #include "update_engine/common/terminator.h" #include "update_engine/common/utils.h" -#include "update_engine/daemon.h" -#include "update_engine/logging.h" using std::string; @@ -63,8 +64,8 @@ int main(int argc, char** argv) { // Done _after_ log file creation. umask(S_IRWXG | S_IRWXO); - chromeos_update_engine::UpdateEngineDaemon update_engine_daemon; - int exit_code = update_engine_daemon.Run(); + auto daemon = chromeos_update_engine::DaemonBase::CreateInstance(); + int exit_code = daemon->Run(); chromeos_update_engine::Subprocess::Get().FlushBufferedLogsAtExit(); diff --git a/metrics_utils.cc b/metrics_utils.cc index 9abc3ef7..34da5a1a 100644 --- a/metrics_utils.cc +++ b/metrics_utils.cc @@ -23,7 +23,6 @@ #include "update_engine/common/clock_interface.h" #include "update_engine/common/constants.h" #include "update_engine/common/utils.h" -#include "update_engine/system_state.h" using base::Time; using base::TimeDelta; @@ -95,6 +94,7 @@ metrics::AttemptResult GetAttemptResult(ErrorCode code) { case ErrorCode::kPostinstallRunnerError: case ErrorCode::kPostinstallBootedFromFirmwareB: case ErrorCode::kPostinstallFirmwareRONotUpdatable: + case ErrorCode::kPostInstallMountError: return metrics::AttemptResult::kPostInstallFailed; case ErrorCode::kUserCanceled: @@ -111,10 +111,6 @@ metrics::AttemptResult GetAttemptResult(ErrorCode code) { case ErrorCode::kDownloadInvalidMetadataSignature: case ErrorCode::kOmahaResponseInvalid: case ErrorCode::kOmahaUpdateIgnoredPerPolicy: - // TODO(deymo): The next two items belong in their own category; they - // should not be counted as internal errors. b/27112092 - case ErrorCode::kOmahaUpdateDeferredPerPolicy: - case ErrorCode::kNonCriticalUpdateInOOBE: case ErrorCode::kOmahaErrorInHTTPResponse: case ErrorCode::kDownloadMetadataSignatureMissingError: case ErrorCode::kOmahaUpdateDeferredForBackoff: @@ -124,8 +120,13 @@ metrics::AttemptResult GetAttemptResult(ErrorCode code) { case ErrorCode::kOmahaUpdateIgnoredOverCellular: case ErrorCode::kNoUpdate: case ErrorCode::kFirstActiveOmahaPingSentPersistenceError: + case ErrorCode::kPackageExcludedFromUpdate: return metrics::AttemptResult::kInternalError; + case ErrorCode::kOmahaUpdateDeferredPerPolicy: + case ErrorCode::kNonCriticalUpdateInOOBE: + return metrics::AttemptResult::kUpdateSkipped; + // Special flags. These can't happen (we mask them out above) but // the compiler doesn't know that. Just break out so we can warn and // return |kInternalError|. @@ -188,6 +189,7 @@ metrics::DownloadErrorCode GetDownloadErrorCode(ErrorCode code) { case ErrorCode::kOmahaResponseHandlerError: case ErrorCode::kFilesystemCopierError: case ErrorCode::kPostinstallRunnerError: + case ErrorCode::kPostInstallMountError: case ErrorCode::kPayloadMismatchedType: case ErrorCode::kInstallDeviceOpenError: case ErrorCode::kKernelDeviceOpenError: @@ -240,6 +242,7 @@ metrics::DownloadErrorCode GetDownloadErrorCode(ErrorCode code) { case ErrorCode::kVerityCalculationError: case ErrorCode::kNotEnoughSpace: case ErrorCode::kDeviceCorrupted: + case ErrorCode::kPackageExcludedFromUpdate: break; // Special flags. These can't happen (we mask them out above) but @@ -280,12 +283,6 @@ metrics::ConnectionType GetConnectionType(ConnectionType type, else return metrics::ConnectionType::kWifi; - case ConnectionType::kWimax: - return metrics::ConnectionType::kWimax; - - case ConnectionType::kBluetooth: - return metrics::ConnectionType::kBluetooth; - case ConnectionType::kCellular: return metrics::ConnectionType::kCellular; } @@ -297,48 +294,6 @@ metrics::ConnectionType GetConnectionType(ConnectionType type, return metrics::ConnectionType::kUnknown; } -bool WallclockDurationHelper(SystemState* system_state, - const std::string& state_variable_key, - TimeDelta* out_duration) { - bool ret = false; - - Time now = system_state->clock()->GetWallclockTime(); - int64_t stored_value; - if (system_state->prefs()->GetInt64(state_variable_key, &stored_value)) { - Time stored_time = Time::FromInternalValue(stored_value); - if (stored_time > now) { - LOG(ERROR) << "Stored time-stamp used for " << state_variable_key - << " is in the future."; - } else { - *out_duration = now - stored_time; - ret = true; - } - } - - if (!system_state->prefs()->SetInt64(state_variable_key, - now.ToInternalValue())) { - LOG(ERROR) << "Error storing time-stamp in " << state_variable_key; - } - - return ret; -} - -bool MonotonicDurationHelper(SystemState* system_state, - int64_t* storage, - TimeDelta* out_duration) { - bool ret = false; - - Time now = system_state->clock()->GetMonotonicTime(); - if (*storage != 0) { - Time stored_time = Time::FromInternalValue(*storage); - *out_duration = now - stored_time; - ret = true; - } - *storage = now.ToInternalValue(); - - return ret; -} - int64_t GetPersistedValue(const std::string& key, PrefsInterface* prefs) { CHECK(prefs); if (!prefs->Exists(key)) @@ -408,8 +363,7 @@ bool LoadAndReportTimeToReboot(MetricsReporterInterface* metrics_reporter, return false; Time system_updated_at = Time::FromInternalValue(stored_value); - base::TimeDelta time_to_reboot = - clock->GetMonotonicTime() - system_updated_at; + TimeDelta time_to_reboot = clock->GetMonotonicTime() - system_updated_at; if (time_to_reboot.ToInternalValue() < 0) { LOG(ERROR) << "time_to_reboot is negative - system_updated_at: " << utils::ToString(system_updated_at); diff --git a/metrics_utils.h b/metrics_utils.h index 8f1aad1e..3aac4e5b 100644 --- a/metrics_utils.h +++ b/metrics_utils.h @@ -22,16 +22,14 @@ #include <base/time/time.h> #include "update_engine/common/clock_interface.h" +#include "update_engine/common/connection_utils.h" #include "update_engine/common/error_code.h" +#include "update_engine/common/metrics_constants.h" +#include "update_engine/common/metrics_reporter_interface.h" #include "update_engine/common/prefs_interface.h" -#include "update_engine/connection_utils.h" -#include "update_engine/metrics_constants.h" -#include "update_engine/metrics_reporter_interface.h" namespace chromeos_update_engine { -class SystemState; - namespace metrics_utils { // Transforms a ErrorCode value into a metrics::DownloadErrorCode. @@ -50,29 +48,6 @@ metrics::AttemptResult GetAttemptResult(ErrorCode code); metrics::ConnectionType GetConnectionType(ConnectionType type, ConnectionTethering tethering); -// This function returns the duration on the wallclock since the last -// time it was called for the same |state_variable_key| value. -// -// If the function returns |true|, the duration (always non-negative) -// is returned in |out_duration|. If the function returns |false| -// something went wrong or there was no previous measurement. -bool WallclockDurationHelper(SystemState* system_state, - const std::string& state_variable_key, - base::TimeDelta* out_duration); - -// This function returns the duration on the monotonic clock since the -// last time it was called for the same |storage| pointer. -// -// You should pass a pointer to a 64-bit integer in |storage| which -// should be initialized to 0. -// -// If the function returns |true|, the duration (always non-negative) -// is returned in |out_duration|. If the function returns |false| -// something went wrong or there was no previous measurement. -bool MonotonicDurationHelper(SystemState* system_state, - int64_t* storage, - base::TimeDelta* out_duration); - // Returns the persisted value from prefs for the given key. It also // validates that the value returned is non-negative. int64_t GetPersistedValue(const std::string& key, PrefsInterface* prefs); diff --git a/metrics_utils_unittest.cc b/metrics_utils_unittest.cc index e7c4c26b..93b48fbd 100644 --- a/metrics_utils_unittest.cc +++ b/metrics_utils_unittest.cc @@ -18,10 +18,6 @@ #include <gtest/gtest.h> -#include "update_engine/common/fake_clock.h" -#include "update_engine/common/fake_prefs.h" -#include "update_engine/fake_system_state.h" - namespace chromeos_update_engine { namespace metrics_utils { @@ -41,12 +37,6 @@ TEST(MetricsUtilsTest, GetConnectionType) { EXPECT_EQ( metrics::ConnectionType::kWifi, GetConnectionType(ConnectionType::kWifi, ConnectionTethering::kUnknown)); - EXPECT_EQ( - metrics::ConnectionType::kWimax, - GetConnectionType(ConnectionType::kWimax, ConnectionTethering::kUnknown)); - EXPECT_EQ(metrics::ConnectionType::kBluetooth, - GetConnectionType(ConnectionType::kBluetooth, - ConnectionTethering::kUnknown)); EXPECT_EQ(metrics::ConnectionType::kCellular, GetConnectionType(ConnectionType::kCellular, ConnectionTethering::kUnknown)); @@ -80,116 +70,5 @@ TEST(MetricsUtilsTest, GetConnectionType) { GetConnectionType(ConnectionType::kWifi, ConnectionTethering::kUnknown)); } -TEST(MetricsUtilsTest, WallclockDurationHelper) { - FakeSystemState fake_system_state; - FakeClock fake_clock; - base::TimeDelta duration; - const std::string state_variable_key = "test-prefs"; - FakePrefs fake_prefs; - - fake_system_state.set_clock(&fake_clock); - fake_system_state.set_prefs(&fake_prefs); - - // Initialize wallclock to 1 sec. - fake_clock.SetWallclockTime(base::Time::FromInternalValue(1000000)); - - // First time called so no previous measurement available. - EXPECT_FALSE(metrics_utils::WallclockDurationHelper( - &fake_system_state, state_variable_key, &duration)); - - // Next time, we should get zero since the clock didn't advance. - EXPECT_TRUE(metrics_utils::WallclockDurationHelper( - &fake_system_state, state_variable_key, &duration)); - EXPECT_EQ(duration.InSeconds(), 0); - - // We can also call it as many times as we want with it being - // considered a failure. - EXPECT_TRUE(metrics_utils::WallclockDurationHelper( - &fake_system_state, state_variable_key, &duration)); - EXPECT_EQ(duration.InSeconds(), 0); - EXPECT_TRUE(metrics_utils::WallclockDurationHelper( - &fake_system_state, state_variable_key, &duration)); - EXPECT_EQ(duration.InSeconds(), 0); - - // Advance the clock one second, then we should get 1 sec on the - // next call and 0 sec on the subsequent call. - fake_clock.SetWallclockTime(base::Time::FromInternalValue(2000000)); - EXPECT_TRUE(metrics_utils::WallclockDurationHelper( - &fake_system_state, state_variable_key, &duration)); - EXPECT_EQ(duration.InSeconds(), 1); - EXPECT_TRUE(metrics_utils::WallclockDurationHelper( - &fake_system_state, state_variable_key, &duration)); - EXPECT_EQ(duration.InSeconds(), 0); - - // Advance clock two seconds and we should get 2 sec and then 0 sec. - fake_clock.SetWallclockTime(base::Time::FromInternalValue(4000000)); - EXPECT_TRUE(metrics_utils::WallclockDurationHelper( - &fake_system_state, state_variable_key, &duration)); - EXPECT_EQ(duration.InSeconds(), 2); - EXPECT_TRUE(metrics_utils::WallclockDurationHelper( - &fake_system_state, state_variable_key, &duration)); - EXPECT_EQ(duration.InSeconds(), 0); - - // There's a possibility that the wallclock can go backwards (NTP - // adjustments, for example) so check that we properly handle this - // case. - fake_clock.SetWallclockTime(base::Time::FromInternalValue(3000000)); - EXPECT_FALSE(metrics_utils::WallclockDurationHelper( - &fake_system_state, state_variable_key, &duration)); - fake_clock.SetWallclockTime(base::Time::FromInternalValue(4000000)); - EXPECT_TRUE(metrics_utils::WallclockDurationHelper( - &fake_system_state, state_variable_key, &duration)); - EXPECT_EQ(duration.InSeconds(), 1); -} - -TEST(MetricsUtilsTest, MonotonicDurationHelper) { - int64_t storage = 0; - FakeSystemState fake_system_state; - FakeClock fake_clock; - base::TimeDelta duration; - - fake_system_state.set_clock(&fake_clock); - - // Initialize monotonic clock to 1 sec. - fake_clock.SetMonotonicTime(base::Time::FromInternalValue(1000000)); - - // First time called so no previous measurement available. - EXPECT_FALSE(metrics_utils::MonotonicDurationHelper( - &fake_system_state, &storage, &duration)); - - // Next time, we should get zero since the clock didn't advance. - EXPECT_TRUE(metrics_utils::MonotonicDurationHelper( - &fake_system_state, &storage, &duration)); - EXPECT_EQ(duration.InSeconds(), 0); - - // We can also call it as many times as we want with it being - // considered a failure. - EXPECT_TRUE(metrics_utils::MonotonicDurationHelper( - &fake_system_state, &storage, &duration)); - EXPECT_EQ(duration.InSeconds(), 0); - EXPECT_TRUE(metrics_utils::MonotonicDurationHelper( - &fake_system_state, &storage, &duration)); - EXPECT_EQ(duration.InSeconds(), 0); - - // Advance the clock one second, then we should get 1 sec on the - // next call and 0 sec on the subsequent call. - fake_clock.SetMonotonicTime(base::Time::FromInternalValue(2000000)); - EXPECT_TRUE(metrics_utils::MonotonicDurationHelper( - &fake_system_state, &storage, &duration)); - EXPECT_EQ(duration.InSeconds(), 1); - EXPECT_TRUE(metrics_utils::MonotonicDurationHelper( - &fake_system_state, &storage, &duration)); - EXPECT_EQ(duration.InSeconds(), 0); - - // Advance clock two seconds and we should get 2 sec and then 0 sec. - fake_clock.SetMonotonicTime(base::Time::FromInternalValue(4000000)); - EXPECT_TRUE(metrics_utils::MonotonicDurationHelper( - &fake_system_state, &storage, &duration)); - EXPECT_EQ(duration.InSeconds(), 2); - EXPECT_TRUE(metrics_utils::MonotonicDurationHelper( - &fake_system_state, &storage, &duration)); - EXPECT_EQ(duration.InSeconds(), 0); -} - } // namespace metrics_utils } // namespace chromeos_update_engine diff --git a/mock_boot_control_hal.h b/mock_boot_control_hal.h deleted file mode 100644 index 4e9cb508..00000000 --- a/mock_boot_control_hal.h +++ /dev/null @@ -1,49 +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 <android/hardware/boot/1.0/IBootControl.h> -#include <stdint.h> - -#include <gmock/gmock.h> - -namespace chromeos_update_engine { - -class MockBootControlHal - : public ::android::hardware::boot::V1_0::IBootControl { - public: - MOCK_METHOD0(getNumberSlots, ::android::hardware::Return<uint32_t>()); - MOCK_METHOD0(getCurrentSlot, ::android::hardware::Return<uint32_t>()); - MOCK_METHOD1(markBootSuccessful, - ::android::hardware::Return<void>(markBootSuccessful_cb)); - MOCK_METHOD2(setActiveBootSlot, - ::android::hardware::Return<void>(uint32_t, - setActiveBootSlot_cb)); - MOCK_METHOD2(setSlotAsUnbootable, - ::android::hardware::Return<void>(uint32_t, - setSlotAsUnbootable_cb)); - MOCK_METHOD1( - isSlotBootable, - ::android::hardware::Return<::android::hardware::boot::V1_0::BoolResult>( - uint32_t)); - MOCK_METHOD1( - isSlotMarkedSuccessful, - ::android::hardware::Return<::android::hardware::boot::V1_0::BoolResult>( - uint32_t)); - MOCK_METHOD2(getSuffix, - ::android::hardware::Return<void>(uint32_t, getSuffix_cb)); -}; - -} // namespace chromeos_update_engine diff --git a/mock_dynamic_partition_control.h b/mock_dynamic_partition_control.h deleted file mode 100644 index 1e4e5fd8..00000000 --- a/mock_dynamic_partition_control.h +++ /dev/null @@ -1,110 +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 <stdint.h> - -#include <memory> -#include <set> -#include <string> - -#include <gmock/gmock.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 { - public: - MOCK_METHOD5(MapPartitionOnDeviceMapper, - bool(const std::string&, - const std::string&, - uint32_t, - bool, - std::string*)); - MOCK_METHOD0(Cleanup, void()); - MOCK_METHOD0(GetDynamicPartitionsFeatureFlag, FeatureFlag()); - MOCK_METHOD5( - PreparePartitionsForUpdate, - bool(uint32_t, uint32_t, const DeltaArchiveManifest&, bool, uint64_t*)); - MOCK_METHOD0(GetVirtualAbFeatureFlag, FeatureFlag()); - MOCK_METHOD1(FinishUpdate, bool(bool)); - MOCK_METHOD0(CleanupSuccessfulUpdate, ErrorCode()); - MOCK_METHOD3(GetCleanupPreviousUpdateAction, - std::unique_ptr<AbstractAction>( - BootControlInterface*, - PrefsInterface*, - CleanupPreviousUpdateActionDelegateInterface*)); -}; - -class MockDynamicPartitionControlAndroid - : public DynamicPartitionControlAndroid { - public: - MOCK_METHOD5(MapPartitionOnDeviceMapper, - bool(const std::string&, - const std::string&, - uint32_t, - bool, - std::string*)); - MOCK_METHOD1(UnmapPartitionOnDeviceMapper, bool(const std::string&)); - 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(GetDynamicPartitionsFeatureFlag, FeatureFlag()); - MOCK_METHOD1(GetSuperPartitionName, std::string(uint32_t)); - MOCK_METHOD0(GetVirtualAbFeatureFlag, FeatureFlag()); - MOCK_METHOD1(FinishUpdate, bool(bool)); - MOCK_METHOD5( - GetSystemOtherPath, - bool(uint32_t, uint32_t, const std::string&, std::string*, bool*)); - MOCK_METHOD2(EraseSystemOtherAvbFooter, bool(uint32_t, uint32_t)); - MOCK_METHOD0(IsAvbEnabledOnSystemOther, std::optional<bool>()); - - 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); - } -}; - -} // namespace chromeos_update_engine diff --git a/mock_libcurl_http_fetcher.h b/mock_libcurl_http_fetcher.h new file mode 100644 index 00000000..a14f9530 --- /dev/null +++ b/mock_libcurl_http_fetcher.h @@ -0,0 +1,37 @@ +// +// 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_MOCK_LIBCURL_HTTP_FETCHER_H_ +#define UPDATE_ENGINE_MOCK_LIBCURL_HTTP_FETCHER_H_ + +#include <gmock/gmock.h> + +#include "update_engine/libcurl_http_fetcher.h" + +namespace chromeos_update_engine { + +class MockLibcurlHttpFetcher : public LibcurlHttpFetcher { + public: + MockLibcurlHttpFetcher(ProxyResolver* proxy_resolver, + HardwareInterface* hardware) + : LibcurlHttpFetcher(proxy_resolver, hardware) {} + + MOCK_METHOD0(GetHttpResponseCode, void()); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_MOCK_LIBCURL_HTTP_FETCHER_H_ diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc deleted file mode 100644 index 1786bcc2..00000000 --- a/omaha_request_action_unittest.cc +++ /dev/null @@ -1,3147 +0,0 @@ -// -// Copyright (C) 2012 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/omaha_request_action.h" - -#include <stdint.h> - -#include <memory> -#include <string> -#include <utility> -#include <vector> - -#include <base/bind.h> -#include <base/files/file_util.h> -#include <base/files/scoped_temp_dir.h> -#include <base/memory/ptr_util.h> -#include <base/strings/string_number_conversions.h> -#include <base/strings/string_util.h> -#include <base/strings/stringprintf.h> -#include <base/time/time.h> -#include <brillo/message_loops/fake_message_loop.h> -#include <brillo/message_loops/message_loop.h> -#include <brillo/message_loops/message_loop_utils.h> -#include <gtest/gtest.h> -#include <policy/libpolicy.h> -#include <policy/mock_libpolicy.h> - -#include "update_engine/common/action_pipe.h" -#include "update_engine/common/constants.h" -#include "update_engine/common/fake_prefs.h" -#include "update_engine/common/hash_calculator.h" -#include "update_engine/common/mock_http_fetcher.h" -#include "update_engine/common/platform_constants.h" -#include "update_engine/common/prefs.h" -#include "update_engine/common/test_utils.h" -#include "update_engine/fake_system_state.h" -#include "update_engine/metrics_reporter_interface.h" -#include "update_engine/mock_connection_manager.h" -#include "update_engine/mock_payload_state.h" -#include "update_engine/omaha_request_params.h" -#include "update_engine/update_manager/rollback_prefs.h" - -using base::Time; -using base::TimeDelta; -using chromeos_update_manager::kRollforwardInfinity; -using std::string; -using std::vector; -using testing::_; -using testing::AllOf; -using testing::AnyNumber; -using testing::DoAll; -using testing::Ge; -using testing::Le; -using testing::NiceMock; -using testing::Return; -using testing::ReturnPointee; -using testing::ReturnRef; -using testing::SaveArg; -using testing::SetArgPointee; - -namespace { - -static_assert(kRollforwardInfinity == 0xfffffffe, - "Don't change the value of kRollforward infinity unless its " - "size has been changed in firmware."); - -const char kCurrentVersion[] = "0.1.0.0"; -const char kTestAppId[] = "test-app-id"; -const char kTestAppId2[] = "test-app2-id"; -const char kTestAppIdSkipUpdatecheck[] = "test-app-id-skip-updatecheck"; - -// This is a helper struct to allow unit tests build an update response with the -// values they care about. -struct FakeUpdateResponse { - string GetRollbackVersionAttributes() const { - return (rollback ? " _rollback=\"true\"" : "") + - (!rollback_firmware_version.empty() - ? " _firmware_version=\"" + rollback_firmware_version + "\"" - : "") + - (!rollback_kernel_version.empty() - ? " _kernel_version=\"" + rollback_kernel_version + "\"" - : ""); - } - - string GetNoUpdateResponse() const { - string entity_str; - if (include_entity) - entity_str = "<!DOCTYPE response [<!ENTITY CrOS \"ChromeOS\">]>"; - return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + entity_str + - "<response protocol=\"3.0\">" - "<daystart elapsed_seconds=\"100\"/>" - "<app appid=\"" + - app_id + "\" " + - (include_cohorts - ? "cohort=\"" + cohort + "\" cohorthint=\"" + cohorthint + - "\" cohortname=\"" + cohortname + "\" " - : "") + - " status=\"ok\">" - "<ping status=\"ok\"/>" - "<updatecheck status=\"noupdate\"/></app>" + - (multi_app_no_update - ? "<app appid=\"" + app_id2 + - "\"><updatecheck status=\"noupdate\"/></app>" - : "") + - "</response>"; - } - - string GetUpdateResponse() const { - return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " - "protocol=\"3.0\">" - "<daystart elapsed_seconds=\"100\"" + - (elapsed_days.empty() ? "" - : (" elapsed_days=\"" + elapsed_days + "\"")) + - "/>" - "<app appid=\"" + - app_id + "\" " + - (include_cohorts - ? "cohort=\"" + cohort + "\" cohorthint=\"" + cohorthint + - "\" cohortname=\"" + cohortname + "\" " - : "") + - " status=\"ok\">" - "<ping status=\"ok\"/><updatecheck status=\"ok\"" + - GetRollbackVersionAttributes() + ">" + "<urls><url codebase=\"" + - codebase + - "\"/></urls>" - "<manifest version=\"" + - version + - "\">" - "<packages><package hash=\"not-used\" name=\"" + - filename + "\" size=\"" + base::Int64ToString(size) + - "\" hash_sha256=\"" + hash + "\"/>" + - (multi_package ? "<package name=\"package2\" size=\"222\" " - "hash_sha256=\"hash2\"/>" - : "") + - "</packages>" - "<actions><action event=\"postinstall\" MetadataSize=\"11" + - (multi_package ? ":22" : "") + "\" MoreInfo=\"" + more_info_url + - "\" Prompt=\"" + prompt + - "\" " - "IsDeltaPayload=\"true" + - (multi_package ? ":false" : "") + - "\" " - "MaxDaysToScatter=\"" + - max_days_to_scatter + - "\" " - "sha256=\"not-used\" " + - (deadline.empty() ? "" : ("deadline=\"" + deadline + "\" ")) + - (disable_p2p_for_downloading ? "DisableP2PForDownloading=\"true\" " - : "") + - (disable_p2p_for_sharing ? "DisableP2PForSharing=\"true\" " : "") + - (powerwash ? "Powerwash=\"true\" " : "") + - "/></actions></manifest></updatecheck></app>" + - (multi_app - ? "<app appid=\"" + app_id2 + "\"" + - (include_cohorts ? " cohort=\"cohort2\"" : "") + - "><updatecheck status=\"ok\"><urls><url codebase=\"" + - codebase2 + "\"/></urls><manifest version=\"" + version2 + - "\"><packages>" - "<package name=\"package3\" size=\"333\" " - "hash_sha256=\"hash3\"/></packages>" - "<actions><action event=\"postinstall\" " + - (multi_app_self_update - ? "noupdate=\"true\" IsDeltaPayload=\"true\" " - : "IsDeltaPayload=\"false\" ") + - "MetadataSize=\"33\"/></actions>" - "</manifest></updatecheck></app>" - : "") + - (multi_app_no_update - ? "<app><updatecheck status=\"noupdate\"/></app>" - : "") + - (multi_app_skip_updatecheck - ? "<app appid=\"" + app_id_skip_updatecheck + "\"></app>" - : "") + - "</response>"; - } - - // Return the payload URL, which is split in two fields in the XML response. - string GetPayloadUrl() { return codebase + filename; } - - string app_id = kTestAppId; - string app_id2 = kTestAppId2; - string app_id_skip_updatecheck = kTestAppIdSkipUpdatecheck; - string current_version = kCurrentVersion; - string version = "1.2.3.4"; - string version2 = "2.3.4.5"; - string more_info_url = "http://more/info"; - string prompt = "true"; - string codebase = "http://code/base/"; - string codebase2 = "http://code/base/2/"; - string filename = "file.signed"; - string hash = "4841534831323334"; - uint64_t size = 123; - string deadline = ""; - string max_days_to_scatter = "7"; - string elapsed_days = "42"; - - // P2P setting defaults to allowed. - bool disable_p2p_for_downloading = false; - bool disable_p2p_for_sharing = false; - - bool powerwash = false; - - // Omaha cohorts settings. - bool include_cohorts = false; - string cohort = ""; - string cohorthint = ""; - string cohortname = ""; - - // Whether to include the CrOS <!ENTITY> in the XML response. - bool include_entity = false; - - // Whether to include more than one app. - bool multi_app = false; - // Whether to include an app with noupdate="true". - bool multi_app_self_update = false; - // Whether to include an additional app with status="noupdate". - bool multi_app_no_update = false; - // Whether to include an additional app with no updatecheck tag. - bool multi_app_skip_updatecheck = false; - // Whether to include more than one package in an app. - bool multi_package = false; - - // Whether the payload is a rollback. - bool rollback = false; - // The verified boot firmware key version for the rollback image. - string rollback_firmware_version = ""; - // The verified boot kernel key version for the rollback image. - string rollback_kernel_version = ""; -}; - -} // namespace - -namespace chromeos_update_engine { - -class OmahaRequestActionTestProcessorDelegate : public ActionProcessorDelegate { - public: - OmahaRequestActionTestProcessorDelegate() - : expected_code_(ErrorCode::kSuccess), - interactive_(false), - test_http_fetcher_headers_(false) {} - ~OmahaRequestActionTestProcessorDelegate() override = default; - - void ProcessingDone(const ActionProcessor* processor, - ErrorCode code) override { - brillo::MessageLoop::current()->BreakLoop(); - } - - void ActionCompleted(ActionProcessor* processor, - AbstractAction* action, - ErrorCode code) override { - // Make sure actions always succeed. - if (action->Type() == OmahaRequestAction::StaticType()) { - EXPECT_EQ(expected_code_, code); - // Check that the headers were set in the fetcher during the action. Note - // that we set this request as "interactive". - auto fetcher = static_cast<const MockHttpFetcher*>( - static_cast<OmahaRequestAction*>(action)->http_fetcher_.get()); - - if (test_http_fetcher_headers_) { - EXPECT_EQ(interactive_ ? "fg" : "bg", - fetcher->GetHeader("X-Goog-Update-Interactivity")); - EXPECT_EQ(kTestAppId, fetcher->GetHeader("X-Goog-Update-AppId")); - EXPECT_NE("", fetcher->GetHeader("X-Goog-Update-Updater")); - } - post_data_ = fetcher->post_data(); - } else if (action->Type() == - ObjectCollectorAction<OmahaResponse>::StaticType()) { - EXPECT_EQ(ErrorCode::kSuccess, code); - auto collector_action = - static_cast<ObjectCollectorAction<OmahaResponse>*>(action); - omaha_response_.reset(new OmahaResponse(collector_action->object())); - EXPECT_TRUE(omaha_response_); - } else { - EXPECT_EQ(ErrorCode::kSuccess, code); - } - } - ErrorCode expected_code_; - brillo::Blob post_data_; - bool interactive_; - bool test_http_fetcher_headers_; - std::unique_ptr<OmahaResponse> omaha_response_; -}; - -class OmahaRequestActionTest : public ::testing::Test { - protected: - void SetUp() override { - request_params_.set_os_sp("service_pack"); - request_params_.set_os_board("x86-generic"); - request_params_.set_app_id(kTestAppId); - request_params_.set_app_version(kCurrentVersion); - request_params_.set_app_lang("en-US"); - request_params_.set_current_channel("unittest"); - request_params_.set_target_channel("unittest"); - request_params_.set_hwid("OEM MODEL 09235 7471"); - request_params_.set_fw_version("ChromeOSFirmware.1.0"); - request_params_.set_ec_version("0X0A1"); - request_params_.set_delta_okay(true); - request_params_.set_interactive(false); - request_params_.set_update_url("http://url"); - request_params_.set_target_version_prefix(""); - request_params_.set_rollback_allowed(false); - request_params_.set_is_powerwash_allowed(false); - request_params_.set_is_install(false); - request_params_.set_dlc_module_ids({}); - - fake_system_state_.set_request_params(&request_params_); - fake_system_state_.set_prefs(&fake_prefs_); - } - - // Returns true iff an output response was obtained from the - // OmahaRequestAction. |prefs| may be null, in which case a local MockPrefs - // is used. |payload_state| may be null, in which case a local mock is used. - // |p2p_manager| may be null, in which case a local mock is used. - // |connection_manager| may be null, in which case a local mock is used. - // out_response may be null. If |fail_http_response_code| is non-negative, - // the transfer will fail with that code. |ping_only| is passed through to the - // OmahaRequestAction constructor. out_post_data may be null; if non-null, the - // post-data received by the mock HttpFetcher is returned. - // - // The |expected_check_result|, |expected_check_reaction| and - // |expected_error_code| parameters are for checking expectations - // about reporting UpdateEngine.Check.{Result,Reaction,DownloadError} - // UMA statistics. Use the appropriate ::kUnset value to specify that - // the given metric should not be reported. - bool TestUpdateCheck(const string& http_response, - int fail_http_response_code, - bool ping_only, - bool is_consumer_device, - int rollback_allowed_milestones, - bool is_policy_loaded, - ErrorCode expected_code, - metrics::CheckResult expected_check_result, - metrics::CheckReaction expected_check_reaction, - metrics::DownloadErrorCode expected_download_error_code, - OmahaResponse* out_response, - brillo::Blob* out_post_data); - - // Overload of TestUpdateCheck that does not supply |is_consumer_device| or - // |rollback_allowed_milestones| which are only required for rollback tests. - bool TestUpdateCheck(const string& http_response, - int fail_http_response_code, - bool ping_only, - ErrorCode expected_code, - metrics::CheckResult expected_check_result, - metrics::CheckReaction expected_check_reaction, - metrics::DownloadErrorCode expected_download_error_code, - OmahaResponse* out_response, - brillo::Blob* out_post_data); - - void TestRollbackCheck(bool is_consumer_device, - int rollback_allowed_milestones, - bool is_policy_loaded, - OmahaResponse* out_response); - - void TestEvent(OmahaEvent* event, - const string& http_response, - brillo::Blob* out_post_data); - - // Runs and checks a ping test. |ping_only| indicates whether it should send - // only a ping or also an updatecheck. - void PingTest(bool ping_only); - - // InstallDate test helper function. - bool InstallDateParseHelper(const string& elapsed_days, - OmahaResponse* response); - - // P2P test helper function. - void P2PTest(bool initial_allow_p2p_for_downloading, - bool initial_allow_p2p_for_sharing, - bool omaha_disable_p2p_for_downloading, - bool omaha_disable_p2p_for_sharing, - bool payload_state_allow_p2p_attempt, - bool expect_p2p_client_lookup, - const string& p2p_client_result_url, - bool expected_allow_p2p_for_downloading, - bool expected_allow_p2p_for_sharing, - const string& expected_p2p_url); - - FakeSystemState fake_system_state_; - FakeUpdateResponse fake_update_response_; - // Used by all tests. - OmahaRequestParams request_params_{&fake_system_state_}; - - FakePrefs fake_prefs_; - - OmahaRequestActionTestProcessorDelegate delegate_; - - bool test_http_fetcher_headers_{false}; -}; - -bool OmahaRequestActionTest::TestUpdateCheck( - const string& http_response, - int fail_http_response_code, - bool ping_only, - bool is_consumer_device, - int rollback_allowed_milestones, - bool is_policy_loaded, - ErrorCode expected_code, - metrics::CheckResult expected_check_result, - metrics::CheckReaction expected_check_reaction, - metrics::DownloadErrorCode expected_download_error_code, - OmahaResponse* out_response, - brillo::Blob* out_post_data) { - brillo::FakeMessageLoop loop(nullptr); - loop.SetAsCurrent(); - auto fetcher = std::make_unique<MockHttpFetcher>( - http_response.data(), http_response.size(), nullptr); - if (fail_http_response_code >= 0) { - fetcher->FailTransfer(fail_http_response_code); - } - // This ensures the tests didn't forget to update fake_system_state_ if they - // are not using the default request_params_. - EXPECT_EQ(&request_params_, fake_system_state_.request_params()); - - auto omaha_request_action = std::make_unique<OmahaRequestAction>( - &fake_system_state_, nullptr, std::move(fetcher), ping_only); - - auto mock_policy_provider = - std::make_unique<NiceMock<policy::MockPolicyProvider>>(); - EXPECT_CALL(*mock_policy_provider, IsConsumerDevice()) - .WillRepeatedly(Return(is_consumer_device)); - - EXPECT_CALL(*mock_policy_provider, device_policy_is_loaded()) - .WillRepeatedly(Return(is_policy_loaded)); - - const policy::MockDevicePolicy device_policy; - const bool get_allowed_milestone_succeeds = rollback_allowed_milestones >= 0; - EXPECT_CALL(device_policy, GetRollbackAllowedMilestones(_)) - .WillRepeatedly(DoAll(SetArgPointee<0>(rollback_allowed_milestones), - Return(get_allowed_milestone_succeeds))); - - EXPECT_CALL(*mock_policy_provider, GetDevicePolicy()) - .WillRepeatedly(ReturnRef(device_policy)); - omaha_request_action->policy_provider_ = std::move(mock_policy_provider); - - delegate_.expected_code_ = expected_code; - delegate_.interactive_ = request_params_.interactive(); - delegate_.test_http_fetcher_headers_ = test_http_fetcher_headers_; - ActionProcessor processor; - processor.set_delegate(&delegate_); - - auto collector_action = - std::make_unique<ObjectCollectorAction<OmahaResponse>>(); - BondActions(omaha_request_action.get(), collector_action.get()); - processor.EnqueueAction(std::move(omaha_request_action)); - processor.EnqueueAction(std::move(collector_action)); - - EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(), - ReportUpdateCheckMetrics(_, _, _, _)) - .Times(AnyNumber()); - - EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(), - ReportUpdateCheckMetrics(_, - expected_check_result, - expected_check_reaction, - expected_download_error_code)) - .Times(ping_only ? 0 : 1); - - loop.PostTask(base::Bind( - [](ActionProcessor* processor) { processor->StartProcessing(); }, - base::Unretained(&processor))); - loop.Run(); - EXPECT_FALSE(loop.PendingTasks()); - if (delegate_.omaha_response_ && out_response) - *out_response = *delegate_.omaha_response_; - if (out_post_data) - *out_post_data = delegate_.post_data_; - return delegate_.omaha_response_ != nullptr; -} - -bool OmahaRequestActionTest::TestUpdateCheck( - const string& http_response, - int fail_http_response_code, - bool ping_only, - ErrorCode expected_code, - metrics::CheckResult expected_check_result, - metrics::CheckReaction expected_check_reaction, - metrics::DownloadErrorCode expected_download_error_code, - OmahaResponse* out_response, - brillo::Blob* out_post_data) { - return TestUpdateCheck(http_response, - fail_http_response_code, - ping_only, - true, // is_consumer_device - 0, // rollback_allowed_milestones - false, // is_policy_loaded - expected_code, - expected_check_result, - expected_check_reaction, - expected_download_error_code, - out_response, - out_post_data); -} - -void OmahaRequestActionTest::TestRollbackCheck(bool is_consumer_device, - int rollback_allowed_milestones, - bool is_policy_loaded, - OmahaResponse* out_response) { - fake_update_response_.deadline = "20101020"; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - is_consumer_device, - rollback_allowed_milestones, - is_policy_loaded, - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - out_response, - nullptr)); - ASSERT_TRUE(out_response->update_exists); -} - -// Tests Event requests -- they should always succeed. |out_post_data| may be -// null; if non-null, the post-data received by the mock HttpFetcher is -// returned. -void OmahaRequestActionTest::TestEvent(OmahaEvent* event, - const string& http_response, - brillo::Blob* out_post_data) { - brillo::FakeMessageLoop loop(nullptr); - loop.SetAsCurrent(); - - auto action = std::make_unique<OmahaRequestAction>( - &fake_system_state_, - event, - std::make_unique<MockHttpFetcher>( - http_response.data(), http_response.size(), nullptr), - false); - ActionProcessor processor; - processor.set_delegate(&delegate_); - processor.EnqueueAction(std::move(action)); - - loop.PostTask(base::Bind( - [](ActionProcessor* processor) { processor->StartProcessing(); }, - base::Unretained(&processor))); - loop.Run(); - EXPECT_FALSE(loop.PendingTasks()); - - if (out_post_data) - *out_post_data = delegate_.post_data_; -} - -TEST_F(OmahaRequestActionTest, RejectEntities) { - OmahaResponse response; - fake_update_response_.include_entity = true; - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLHasEntityDecl, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, NoUpdateTest) { - OmahaResponse response; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, MultiAppNoUpdateTest) { - OmahaResponse response; - fake_update_response_.multi_app_no_update = true; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, MultiAppNoPartialUpdateTest) { - OmahaResponse response; - fake_update_response_.multi_app_no_update = true; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, NoSelfUpdateTest) { - OmahaResponse response; - ASSERT_TRUE(TestUpdateCheck( - "<response><app><updatecheck status=\"ok\"><manifest><actions><action " - "event=\"postinstall\" noupdate=\"true\"/></actions>" - "</manifest></updatecheck></app></response>", - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -// Test that all the values in the response are parsed in a normal update -// response. -TEST_F(OmahaRequestActionTest, ValidUpdateTest) { - OmahaResponse response; - fake_update_response_.deadline = "20101020"; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); - EXPECT_EQ(fake_update_response_.version, response.version); - EXPECT_EQ("", response.system_version); - EXPECT_EQ(fake_update_response_.GetPayloadUrl(), - response.packages[0].payload_urls[0]); - EXPECT_EQ(fake_update_response_.more_info_url, response.more_info_url); - EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash); - EXPECT_EQ(fake_update_response_.size, response.packages[0].size); - EXPECT_EQ(true, response.packages[0].is_delta); - EXPECT_EQ(fake_update_response_.prompt == "true", response.prompt); - EXPECT_EQ(fake_update_response_.deadline, response.deadline); - EXPECT_FALSE(response.powerwash_required); - // Omaha cohort attributes are not set in the response, so they should not be - // persisted. - EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohort)); - EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohortHint)); - EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohortName)); -} - -TEST_F(OmahaRequestActionTest, MultiPackageUpdateTest) { - OmahaResponse response; - fake_update_response_.multi_package = true; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); - EXPECT_EQ(fake_update_response_.version, response.version); - EXPECT_EQ(fake_update_response_.GetPayloadUrl(), - response.packages[0].payload_urls[0]); - EXPECT_EQ(fake_update_response_.codebase + "package2", - response.packages[1].payload_urls[0]); - EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash); - EXPECT_EQ(fake_update_response_.size, response.packages[0].size); - EXPECT_EQ(true, response.packages[0].is_delta); - EXPECT_EQ(11u, response.packages[0].metadata_size); - ASSERT_EQ(2u, response.packages.size()); - EXPECT_EQ(string("hash2"), response.packages[1].hash); - EXPECT_EQ(222u, response.packages[1].size); - EXPECT_EQ(22u, response.packages[1].metadata_size); - EXPECT_EQ(false, response.packages[1].is_delta); -} - -TEST_F(OmahaRequestActionTest, MultiAppUpdateTest) { - OmahaResponse response; - fake_update_response_.multi_app = true; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); - EXPECT_EQ(fake_update_response_.version, response.version); - EXPECT_EQ(fake_update_response_.GetPayloadUrl(), - response.packages[0].payload_urls[0]); - EXPECT_EQ(fake_update_response_.codebase2 + "package3", - response.packages[1].payload_urls[0]); - EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash); - EXPECT_EQ(fake_update_response_.size, response.packages[0].size); - EXPECT_EQ(11u, response.packages[0].metadata_size); - EXPECT_EQ(true, response.packages[0].is_delta); - ASSERT_EQ(2u, response.packages.size()); - EXPECT_EQ(string("hash3"), response.packages[1].hash); - EXPECT_EQ(333u, response.packages[1].size); - EXPECT_EQ(33u, response.packages[1].metadata_size); - EXPECT_EQ(false, response.packages[1].is_delta); -} - -TEST_F(OmahaRequestActionTest, MultiAppAndSystemUpdateTest) { - OmahaResponse response; - fake_update_response_.multi_app = true; - // trigger the lining up of the app and system versions - request_params_.set_system_app_id(fake_update_response_.app_id2); - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); - EXPECT_EQ(fake_update_response_.version, response.version); - EXPECT_EQ(fake_update_response_.version2, response.system_version); - EXPECT_EQ(fake_update_response_.GetPayloadUrl(), - response.packages[0].payload_urls[0]); - EXPECT_EQ(fake_update_response_.codebase2 + "package3", - response.packages[1].payload_urls[0]); - EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash); - EXPECT_EQ(fake_update_response_.size, response.packages[0].size); - EXPECT_EQ(11u, response.packages[0].metadata_size); - EXPECT_EQ(true, response.packages[0].is_delta); - ASSERT_EQ(2u, response.packages.size()); - EXPECT_EQ(string("hash3"), response.packages[1].hash); - EXPECT_EQ(333u, response.packages[1].size); - EXPECT_EQ(33u, response.packages[1].metadata_size); - EXPECT_EQ(false, response.packages[1].is_delta); -} - -TEST_F(OmahaRequestActionTest, MultiAppPartialUpdateTest) { - OmahaResponse response; - fake_update_response_.multi_app = true; - fake_update_response_.multi_app_self_update = true; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); - EXPECT_EQ(fake_update_response_.version, response.version); - EXPECT_EQ("", response.system_version); - EXPECT_EQ(fake_update_response_.GetPayloadUrl(), - response.packages[0].payload_urls[0]); - EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash); - EXPECT_EQ(fake_update_response_.size, response.packages[0].size); - EXPECT_EQ(11u, response.packages[0].metadata_size); - ASSERT_EQ(2u, response.packages.size()); - EXPECT_EQ(string("hash3"), response.packages[1].hash); - EXPECT_EQ(333u, response.packages[1].size); - EXPECT_EQ(33u, response.packages[1].metadata_size); - EXPECT_EQ(true, response.packages[1].is_delta); -} - -TEST_F(OmahaRequestActionTest, MultiAppMultiPackageUpdateTest) { - OmahaResponse response; - fake_update_response_.multi_app = true; - fake_update_response_.multi_package = true; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); - EXPECT_EQ(fake_update_response_.version, response.version); - EXPECT_EQ("", response.system_version); - EXPECT_EQ(fake_update_response_.GetPayloadUrl(), - response.packages[0].payload_urls[0]); - EXPECT_EQ(fake_update_response_.codebase + "package2", - response.packages[1].payload_urls[0]); - EXPECT_EQ(fake_update_response_.codebase2 + "package3", - response.packages[2].payload_urls[0]); - EXPECT_EQ(fake_update_response_.hash, response.packages[0].hash); - EXPECT_EQ(fake_update_response_.size, response.packages[0].size); - EXPECT_EQ(11u, response.packages[0].metadata_size); - EXPECT_EQ(true, response.packages[0].is_delta); - ASSERT_EQ(3u, response.packages.size()); - EXPECT_EQ(string("hash2"), response.packages[1].hash); - EXPECT_EQ(222u, response.packages[1].size); - EXPECT_EQ(22u, response.packages[1].metadata_size); - EXPECT_EQ(false, response.packages[1].is_delta); - EXPECT_EQ(string("hash3"), response.packages[2].hash); - EXPECT_EQ(333u, response.packages[2].size); - EXPECT_EQ(33u, response.packages[2].metadata_size); - EXPECT_EQ(false, response.packages[2].is_delta); -} - -TEST_F(OmahaRequestActionTest, PowerwashTest) { - OmahaResponse response; - fake_update_response_.powerwash = true; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); - EXPECT_TRUE(response.powerwash_required); -} - -TEST_F(OmahaRequestActionTest, ExtraHeadersSentInteractiveTest) { - OmahaResponse response; - request_params_.set_interactive(true); - test_http_fetcher_headers_ = true; - ASSERT_FALSE(TestUpdateCheck("invalid xml>", - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLParseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, ExtraHeadersSentNoInteractiveTest) { - OmahaResponse response; - request_params_.set_interactive(false); - test_http_fetcher_headers_ = true; - ASSERT_FALSE(TestUpdateCheck("invalid xml>", - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLParseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByConnection) { - OmahaResponse response; - // Set up a connection manager that doesn't allow a valid update over - // the current ethernet connection. - MockConnectionManager mock_cm; - fake_system_state_.set_connection_manager(&mock_cm); - - EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) - .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kEthernet), - SetArgPointee<1>(ConnectionTethering::kUnknown), - Return(true))); - EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kEthernet, _)) - .WillRepeatedly(Return(false)); - - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kOmahaUpdateIgnoredPerPolicy, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kIgnored, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, ValidUpdateOverCellularAllowedByDevicePolicy) { - // This test tests that update over cellular is allowed as device policy - // says yes. - OmahaResponse response; - MockConnectionManager mock_cm; - - fake_system_state_.set_connection_manager(&mock_cm); - - EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) - .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular), - SetArgPointee<1>(ConnectionTethering::kUnknown), - Return(true))); - EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet()) - .WillRepeatedly(Return(true)); - EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _)) - .WillRepeatedly(Return(true)); - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, ValidUpdateOverCellularBlockedByDevicePolicy) { - // This test tests that update over cellular is blocked as device policy - // says no. - OmahaResponse response; - MockConnectionManager mock_cm; - - fake_system_state_.set_connection_manager(&mock_cm); - - EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) - .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular), - SetArgPointee<1>(ConnectionTethering::kUnknown), - Return(true))); - EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet()) - .WillRepeatedly(Return(true)); - EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _)) - .WillRepeatedly(Return(false)); - - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kOmahaUpdateIgnoredPerPolicy, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kIgnored, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, - ValidUpdateOverCellularAllowedByUserPermissionTrue) { - // This test tests that, when device policy is not set, update over cellular - // is allowed as permission for update over cellular is set to true. - OmahaResponse response; - MockConnectionManager mock_cm; - - fake_prefs_.SetBoolean(kPrefsUpdateOverCellularPermission, true); - fake_system_state_.set_connection_manager(&mock_cm); - - EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) - .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular), - SetArgPointee<1>(ConnectionTethering::kUnknown), - Return(true))); - EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet()) - .WillRepeatedly(Return(false)); - EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _)) - .WillRepeatedly(Return(true)); - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, - ValidUpdateOverCellularBlockedByUpdateTargetNotMatch) { - // This test tests that, when device policy is not set and permission for - // update over cellular is set to false or does not exist, update over - // cellular is blocked as update target does not match the omaha response. - OmahaResponse response; - MockConnectionManager mock_cm; - // A version different from the version in omaha response. - string diff_version = "99.99.99"; - // A size different from the size in omaha response. - int64_t diff_size = 999; - - fake_prefs_.SetString(kPrefsUpdateOverCellularTargetVersion, diff_version); - fake_prefs_.SetInt64(kPrefsUpdateOverCellularTargetSize, diff_size); - // This test tests cellular (3G) being the only connection type being allowed. - fake_system_state_.set_connection_manager(&mock_cm); - - EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) - .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular), - SetArgPointee<1>(ConnectionTethering::kUnknown), - Return(true))); - EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet()) - .WillRepeatedly(Return(false)); - EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _)) - .WillRepeatedly(Return(true)); - - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kOmahaUpdateIgnoredOverCellular, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kIgnored, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, - ValidUpdateOverCellularAllowedByUpdateTargetMatch) { - // This test tests that, when device policy is not set and permission for - // update over cellular is set to false or does not exist, update over - // cellular is allowed as update target matches the omaha response. - OmahaResponse response; - MockConnectionManager mock_cm; - // A version same as the version in omaha response. - string new_version = fake_update_response_.version; - // A size same as the size in omaha response. - int64_t new_size = fake_update_response_.size; - - fake_prefs_.SetString(kPrefsUpdateOverCellularTargetVersion, new_version); - fake_prefs_.SetInt64(kPrefsUpdateOverCellularTargetSize, new_size); - fake_system_state_.set_connection_manager(&mock_cm); - - EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) - .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular), - SetArgPointee<1>(ConnectionTethering::kUnknown), - Return(true))); - EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet()) - .WillRepeatedly(Return(false)); - EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _)) - .WillRepeatedly(Return(true)); - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByRollback) { - string rollback_version = "1234.0.0"; - OmahaResponse response; - - MockPayloadState mock_payload_state; - fake_system_state_.set_payload_state(&mock_payload_state); - - EXPECT_CALL(mock_payload_state, GetRollbackVersion()) - .WillRepeatedly(Return(rollback_version)); - - fake_update_response_.version = rollback_version; - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kOmahaUpdateIgnoredPerPolicy, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kIgnored, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -// Verify that update checks called during OOBE will not try to download an -// update if the response doesn't include the deadline field. -TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBE) { - OmahaResponse response; - fake_system_state_.fake_hardware()->UnsetIsOOBEComplete(); - - // TODO(senj): set better default value for metrics::checkresult in - // OmahaRequestAction::ActionCompleted. - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kNonCriticalUpdateInOOBE, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -// Verify that the IsOOBEComplete() value is ignored when the OOBE flow is not -// enabled. -TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBEDisabled) { - OmahaResponse response; - fake_system_state_.fake_hardware()->UnsetIsOOBEComplete(); - fake_system_state_.fake_hardware()->SetIsOOBEEnabled(false); - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); -} - -// Verify that update checks called during OOBE will still try to download an -// update if the response includes the deadline field. -TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBEDeadlineSet) { - OmahaResponse response; - fake_system_state_.fake_hardware()->UnsetIsOOBEComplete(); - fake_update_response_.deadline = "20101020"; - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); -} - -// Verify that update checks called during OOBE will not try to download an -// update if a rollback happened, even when the response includes the deadline -// field. -TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBERollback) { - OmahaResponse response; - fake_system_state_.fake_hardware()->UnsetIsOOBEComplete(); - fake_update_response_.deadline = "20101020"; - EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetRollbackHappened()) - .WillOnce(Return(true)); - - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kNonCriticalUpdateInOOBE, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -// Verify that non-critical updates are skipped by reporting the -// kNonCriticalUpdateInOOBE error code when attempted over cellular network - -// i.e. when the update would need user permission. Note that reporting -// kOmahaUpdateIgnoredOverCellular error in this case might cause undesired UX -// in OOBE (warning the user about an update that will be skipped). -TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesInOOBEOverCellular) { - OmahaResponse response; - fake_system_state_.fake_hardware()->UnsetIsOOBEComplete(); - - MockConnectionManager mock_cm; - fake_system_state_.set_connection_manager(&mock_cm); - - EXPECT_CALL(mock_cm, GetConnectionProperties(_, _)) - .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular), - SetArgPointee<1>(ConnectionTethering::kUnknown), - Return(true))); - EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet()) - .WillRepeatedly(Return(false)); - - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kNonCriticalUpdateInOOBE, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, WallClockBasedWaitAloneCausesScattering) { - OmahaResponse response; - request_params_.set_wall_clock_based_wait_enabled(true); - request_params_.set_update_check_count_wait_enabled(false); - request_params_.set_waiting_period(TimeDelta::FromDays(2)); - - fake_system_state_.fake_clock()->SetWallclockTime(Time::Now()); - - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kOmahaUpdateDeferredPerPolicy, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kDeferring, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); - - // Verify if we are interactive check we don't defer. - request_params_.set_interactive(true); - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, NoWallClockBasedWaitCausesNoScattering) { - OmahaResponse response; - request_params_.set_wall_clock_based_wait_enabled(false); - request_params_.set_waiting_period(TimeDelta::FromDays(2)); - request_params_.set_update_check_count_wait_enabled(true); - request_params_.set_min_update_checks_needed(1); - request_params_.set_max_update_checks_allowed(8); - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, ZeroMaxDaysToScatterCausesNoScattering) { - OmahaResponse response; - request_params_.set_wall_clock_based_wait_enabled(true); - request_params_.set_waiting_period(TimeDelta::FromDays(2)); - request_params_.set_update_check_count_wait_enabled(true); - request_params_.set_min_update_checks_needed(1); - request_params_.set_max_update_checks_allowed(8); - - fake_update_response_.max_days_to_scatter = "0"; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, ZeroUpdateCheckCountCausesNoScattering) { - OmahaResponse response; - request_params_.set_wall_clock_based_wait_enabled(true); - request_params_.set_waiting_period(TimeDelta()); - request_params_.set_update_check_count_wait_enabled(true); - request_params_.set_min_update_checks_needed(0); - request_params_.set_max_update_checks_allowed(0); - - fake_system_state_.fake_clock()->SetWallclockTime(Time::Now()); - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - - int64_t count; - ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count)); - ASSERT_EQ(count, 0); - EXPECT_TRUE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, NonZeroUpdateCheckCountCausesScattering) { - OmahaResponse response; - request_params_.set_wall_clock_based_wait_enabled(true); - request_params_.set_waiting_period(TimeDelta()); - request_params_.set_update_check_count_wait_enabled(true); - request_params_.set_min_update_checks_needed(1); - request_params_.set_max_update_checks_allowed(8); - - fake_system_state_.fake_clock()->SetWallclockTime(Time::Now()); - - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kOmahaUpdateDeferredPerPolicy, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kDeferring, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - - int64_t count; - ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count)); - ASSERT_GT(count, 0); - EXPECT_FALSE(response.update_exists); - - // Verify if we are interactive check we don't defer. - request_params_.set_interactive(true); - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, ExistingUpdateCheckCountCausesScattering) { - OmahaResponse response; - request_params_.set_wall_clock_based_wait_enabled(true); - request_params_.set_waiting_period(TimeDelta()); - request_params_.set_update_check_count_wait_enabled(true); - request_params_.set_min_update_checks_needed(1); - request_params_.set_max_update_checks_allowed(8); - - fake_system_state_.fake_clock()->SetWallclockTime(Time::Now()); - - ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsUpdateCheckCount, 5)); - - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kOmahaUpdateDeferredPerPolicy, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kDeferring, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - - int64_t count; - ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count)); - // count remains the same, as the decrementing happens in update_attempter - // which this test doesn't exercise. - ASSERT_EQ(count, 5); - EXPECT_FALSE(response.update_exists); - - // Verify if we are interactive check we don't defer. - request_params_.set_interactive(true); - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, StagingTurnedOnCausesScattering) { - // If staging is on, the value for max days to scatter should be ignored, and - // staging's scatter value should be used. - OmahaResponse response; - request_params_.set_wall_clock_based_wait_enabled(true); - request_params_.set_waiting_period(TimeDelta::FromDays(6)); - request_params_.set_update_check_count_wait_enabled(false); - - fake_system_state_.fake_clock()->SetWallclockTime(Time::Now()); - - ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsWallClockStagingWaitPeriod, 6)); - // This should not prevent scattering due to staging. - fake_update_response_.max_days_to_scatter = "0"; - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kOmahaUpdateDeferredPerPolicy, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kDeferring, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); - - // Interactive updates should not be affected. - request_params_.set_interactive(true); - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, CohortsArePersisted) { - OmahaResponse response; - fake_update_response_.include_cohorts = true; - fake_update_response_.cohort = "s/154454/8479665"; - fake_update_response_.cohorthint = "please-put-me-on-beta"; - fake_update_response_.cohortname = "stable"; - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - - string value; - EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value)); - EXPECT_EQ(fake_update_response_.cohort, value); - - EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value)); - EXPECT_EQ(fake_update_response_.cohorthint, value); - - EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value)); - EXPECT_EQ(fake_update_response_.cohortname, value); -} - -TEST_F(OmahaRequestActionTest, CohortsAreUpdated) { - OmahaResponse response; - EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohort, "old_value")); - EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohortHint, "old_hint")); - EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohortName, "old_name")); - fake_update_response_.include_cohorts = true; - fake_update_response_.cohort = "s/154454/8479665"; - fake_update_response_.cohorthint = "please-put-me-on-beta"; - fake_update_response_.cohortname = ""; - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - - string value; - EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value)); - EXPECT_EQ(fake_update_response_.cohort, value); - - EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value)); - EXPECT_EQ(fake_update_response_.cohorthint, value); - - EXPECT_FALSE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value)); -} - -TEST_F(OmahaRequestActionTest, CohortsAreNotModifiedWhenMissing) { - OmahaResponse response; - EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohort, "old_value")); - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - - string value; - EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value)); - EXPECT_EQ("old_value", value); - - EXPECT_FALSE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value)); - EXPECT_FALSE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value)); -} - -TEST_F(OmahaRequestActionTest, CohortsArePersistedWhenNoUpdate) { - OmahaResponse response; - fake_update_response_.include_cohorts = true; - fake_update_response_.cohort = "s/154454/8479665"; - fake_update_response_.cohorthint = "please-put-me-on-beta"; - fake_update_response_.cohortname = "stable"; - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - - string value; - EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value)); - EXPECT_EQ(fake_update_response_.cohort, value); - - EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value)); - EXPECT_EQ(fake_update_response_.cohorthint, value); - - EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value)); - EXPECT_EQ(fake_update_response_.cohortname, value); -} - -TEST_F(OmahaRequestActionTest, MultiAppCohortTest) { - OmahaResponse response; - fake_update_response_.multi_app = true; - fake_update_response_.include_cohorts = true; - fake_update_response_.cohort = "s/154454/8479665"; - fake_update_response_.cohorthint = "please-put-me-on-beta"; - fake_update_response_.cohortname = "stable"; - - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - - string value; - EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value)); - EXPECT_EQ(fake_update_response_.cohort, value); - - EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value)); - EXPECT_EQ(fake_update_response_.cohorthint, value); - - EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value)); - EXPECT_EQ(fake_update_response_.cohortname, value); -} - -TEST_F(OmahaRequestActionTest, NoOutputPipeTest) { - const string http_response(fake_update_response_.GetNoUpdateResponse()); - - brillo::FakeMessageLoop loop(nullptr); - loop.SetAsCurrent(); - - auto action = std::make_unique<OmahaRequestAction>( - &fake_system_state_, - nullptr, - std::make_unique<MockHttpFetcher>( - http_response.data(), http_response.size(), nullptr), - false); - ActionProcessor processor; - processor.set_delegate(&delegate_); - processor.EnqueueAction(std::move(action)); - - loop.PostTask(base::Bind( - [](ActionProcessor* processor) { processor->StartProcessing(); }, - base::Unretained(&processor))); - loop.Run(); - EXPECT_FALSE(loop.PendingTasks()); - EXPECT_FALSE(processor.IsRunning()); -} - -TEST_F(OmahaRequestActionTest, InvalidXmlTest) { - OmahaResponse response; - ASSERT_FALSE(TestUpdateCheck("invalid xml>", - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLParseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, EmptyResponseTest) { - OmahaResponse response; - ASSERT_FALSE(TestUpdateCheck("", - -1, - false, // ping_only - ErrorCode::kOmahaRequestEmptyResponseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, MissingStatusTest) { - OmahaResponse response; - ASSERT_FALSE(TestUpdateCheck( - "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">" - "<daystart elapsed_seconds=\"100\"/>" - "<app appid=\"foo\" status=\"ok\">" - "<ping status=\"ok\"/>" - "<updatecheck/></app></response>", - -1, - false, // ping_only - ErrorCode::kOmahaResponseInvalid, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, InvalidStatusTest) { - OmahaResponse response; - ASSERT_FALSE(TestUpdateCheck( - "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">" - "<daystart elapsed_seconds=\"100\"/>" - "<app appid=\"foo\" status=\"ok\">" - "<ping status=\"ok\"/>" - "<updatecheck status=\"InvalidStatusTest\"/></app></response>", - -1, - false, // ping_only - ErrorCode::kOmahaResponseInvalid, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, MissingNodesetTest) { - OmahaResponse response; - ASSERT_FALSE(TestUpdateCheck( - "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">" - "<daystart elapsed_seconds=\"100\"/>" - "<app appid=\"foo\" status=\"ok\">" - "<ping status=\"ok\"/>" - "</app></response>", - -1, - false, // ping_only - ErrorCode::kOmahaResponseInvalid, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, MissingFieldTest) { - string input_response = - "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">" - "<daystart elapsed_seconds=\"100\"/>" - // the appid needs to match that in the request params - "<app appid=\"" + - fake_update_response_.app_id + - "\" status=\"ok\">" - "<updatecheck status=\"ok\">" - "<urls><url codebase=\"http://missing/field/test/\"/></urls>" - "<manifest version=\"10.2.3.4\">" - "<packages><package hash=\"not-used\" name=\"f\" " - "size=\"587\" hash_sha256=\"lkq34j5345\"/></packages>" - "<actions><action event=\"postinstall\" " - "Prompt=\"false\" " - "IsDeltaPayload=\"false\" " - "sha256=\"not-used\" " - "/></actions></manifest></updatecheck></app></response>"; - LOG(INFO) << "Input Response = " << input_response; - - OmahaResponse response; - ASSERT_TRUE(TestUpdateCheck(input_response, - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); - EXPECT_EQ("10.2.3.4", response.version); - EXPECT_EQ("http://missing/field/test/f", - response.packages[0].payload_urls[0]); - EXPECT_EQ("", response.more_info_url); - EXPECT_EQ("lkq34j5345", response.packages[0].hash); - EXPECT_EQ(587u, response.packages[0].size); - EXPECT_FALSE(response.prompt); - EXPECT_TRUE(response.deadline.empty()); -} - -namespace { -class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate { - public: - void ProcessingStopped(const ActionProcessor* processor) { - brillo::MessageLoop::current()->BreakLoop(); - } -}; - -void TerminateTransferTestStarter(ActionProcessor* processor) { - processor->StartProcessing(); - CHECK(processor->IsRunning()); - processor->StopProcessing(); -} -} // namespace - -TEST_F(OmahaRequestActionTest, TerminateTransferTest) { - brillo::FakeMessageLoop loop(nullptr); - loop.SetAsCurrent(); - - string http_response("doesn't matter"); - auto action = std::make_unique<OmahaRequestAction>( - &fake_system_state_, - nullptr, - std::make_unique<MockHttpFetcher>( - http_response.data(), http_response.size(), nullptr), - false); - TerminateEarlyTestProcessorDelegate delegate; - ActionProcessor processor; - processor.set_delegate(&delegate); - processor.EnqueueAction(std::move(action)); - - loop.PostTask(base::Bind(&TerminateTransferTestStarter, &processor)); - loop.Run(); - EXPECT_FALSE(loop.PendingTasks()); -} - -TEST_F(OmahaRequestActionTest, XmlEncodeTest) { - string output; - EXPECT_TRUE(XmlEncode("ab", &output)); - EXPECT_EQ("ab", output); - EXPECT_TRUE(XmlEncode("a<b", &output)); - EXPECT_EQ("a<b", output); - EXPECT_TRUE(XmlEncode("<&>\"\'\\", &output)); - EXPECT_EQ("<&>"'\\", output); - EXPECT_TRUE(XmlEncode("<&>", &output)); - EXPECT_EQ("&lt;&amp;&gt;", output); - // Check that unterminated UTF-8 strings are handled properly. - EXPECT_FALSE(XmlEncode("\xc2", &output)); - // Fail with invalid ASCII-7 chars. - EXPECT_FALSE(XmlEncode("This is an 'n' with a tilde: \xc3\xb1", &output)); -} - -TEST_F(OmahaRequestActionTest, XmlEncodeWithDefaultTest) { - EXPECT_EQ("<&>", XmlEncodeWithDefault("<&>", "something else")); - EXPECT_EQ("<not escaped>", XmlEncodeWithDefault("\xc2", "<not escaped>")); -} - -TEST_F(OmahaRequestActionTest, XmlEncodeIsUsedForParams) { - brillo::Blob post_data; - - // Make sure XML Encode is being called on the params - request_params_.set_os_sp("testtheservice_pack>"); - request_params_.set_os_board("x86 generic<id"); - request_params_.set_current_channel("unittest_track<"); - request_params_.set_target_channel("unittest_track<"); - request_params_.set_hwid("<OEM MODEL>"); - fake_prefs_.SetString(kPrefsOmahaCohort, "evil\nstring"); - fake_prefs_.SetString(kPrefsOmahaCohortHint, "evil&string\\"); - fake_prefs_.SetString( - kPrefsOmahaCohortName, - base::JoinString(vector<string>(100, "My spoon is too big."), " ")); - OmahaResponse response; - ASSERT_FALSE(TestUpdateCheck("invalid xml>", - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLParseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - &post_data)); - // convert post_data to string - string post_str(post_data.begin(), post_data.end()); - EXPECT_NE(string::npos, post_str.find("testtheservice_pack>")); - EXPECT_EQ(string::npos, post_str.find("testtheservice_pack>")); - EXPECT_NE(string::npos, post_str.find("x86 generic<id")); - EXPECT_EQ(string::npos, post_str.find("x86 generic<id")); - EXPECT_NE(string::npos, post_str.find("unittest_track&lt;")); - EXPECT_EQ(string::npos, post_str.find("unittest_track<")); - EXPECT_NE(string::npos, post_str.find("<OEM MODEL>")); - EXPECT_EQ(string::npos, post_str.find("<OEM MODEL>")); - EXPECT_NE(string::npos, post_str.find("cohort=\"evil\nstring\"")); - EXPECT_EQ(string::npos, post_str.find("cohorthint=\"evil&string\\\"")); - EXPECT_NE(string::npos, post_str.find("cohorthint=\"evil&string\\\"")); - // Values from Prefs that are too big are removed from the XML instead of - // encoded. - EXPECT_EQ(string::npos, post_str.find("cohortname=")); -} - -TEST_F(OmahaRequestActionTest, XmlDecodeTest) { - OmahaResponse response; - fake_update_response_.deadline = "<20110101"; - fake_update_response_.more_info_url = "testthe<url"; - fake_update_response_.codebase = "testthe&codebase/"; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - - EXPECT_EQ("testthe<url", response.more_info_url); - EXPECT_EQ("testthe&codebase/file.signed", - response.packages[0].payload_urls[0]); - EXPECT_EQ("<20110101", response.deadline); -} - -TEST_F(OmahaRequestActionTest, ParseIntTest) { - OmahaResponse response; - // overflows int32_t: - fake_update_response_.size = 123123123123123ull; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - - EXPECT_EQ(fake_update_response_.size, response.packages[0].size); -} - -TEST_F(OmahaRequestActionTest, FormatUpdateCheckOutputTest) { - brillo::Blob post_data; - NiceMock<MockPrefs> prefs; - fake_system_state_.set_prefs(&prefs); - - EXPECT_CALL(prefs, GetString(kPrefsPreviousVersion, _)) - .WillOnce(DoAll(SetArgPointee<1>(string("")), Return(true))); - // An existing but empty previous version means that we didn't reboot to a new - // update, therefore, no need to update the previous version. - EXPECT_CALL(prefs, SetString(kPrefsPreviousVersion, _)).Times(0); - ASSERT_FALSE(TestUpdateCheck("invalid xml>", - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLParseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, // response - &post_data)); - // convert post_data to string - string post_str(post_data.begin(), post_data.end()); - EXPECT_NE( - post_str.find(" <ping active=\"1\" a=\"-1\" r=\"-1\"></ping>\n" - " <updatecheck></updatecheck>\n"), - string::npos); - EXPECT_NE(post_str.find("hardware_class=\"OEM MODEL 09235 7471\""), - string::npos); - EXPECT_NE(post_str.find("fw_version=\"ChromeOSFirmware.1.0\""), string::npos); - EXPECT_NE(post_str.find("ec_version=\"0X0A1\""), string::npos); - // No <event> tag should be sent if we didn't reboot to an update. - EXPECT_EQ(post_str.find("<event"), string::npos); -} - -TEST_F(OmahaRequestActionTest, FormatSuccessEventOutputTest) { - brillo::Blob post_data; - TestEvent(new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted), - "invalid xml>", - &post_data); - // convert post_data to string - string post_str(post_data.begin(), post_data.end()); - string expected_event = base::StringPrintf( - " <event eventtype=\"%d\" eventresult=\"%d\"></event>\n", - OmahaEvent::kTypeUpdateDownloadStarted, - OmahaEvent::kResultSuccess); - EXPECT_NE(post_str.find(expected_event), string::npos); - EXPECT_EQ(post_str.find("ping"), string::npos); - EXPECT_EQ(post_str.find("updatecheck"), string::npos); -} - -TEST_F(OmahaRequestActionTest, FormatErrorEventOutputTest) { - brillo::Blob post_data; - TestEvent(new OmahaEvent(OmahaEvent::kTypeDownloadComplete, - OmahaEvent::kResultError, - ErrorCode::kError), - "invalid xml>", - &post_data); - // convert post_data to string - string post_str(post_data.begin(), post_data.end()); - string expected_event = base::StringPrintf( - " <event eventtype=\"%d\" eventresult=\"%d\" " - "errorcode=\"%d\"></event>\n", - OmahaEvent::kTypeDownloadComplete, - OmahaEvent::kResultError, - static_cast<int>(ErrorCode::kError)); - EXPECT_NE(post_str.find(expected_event), string::npos); - EXPECT_EQ(post_str.find("updatecheck"), string::npos); -} - -TEST_F(OmahaRequestActionTest, IsEventTest) { - string http_response("doesn't matter"); - OmahaRequestAction update_check_action( - &fake_system_state_, - nullptr, - std::make_unique<MockHttpFetcher>( - http_response.data(), http_response.size(), nullptr), - false); - EXPECT_FALSE(update_check_action.IsEvent()); - - OmahaRequestAction event_action( - &fake_system_state_, - new OmahaEvent(OmahaEvent::kTypeUpdateComplete), - std::make_unique<MockHttpFetcher>( - http_response.data(), http_response.size(), nullptr), - false); - EXPECT_TRUE(event_action.IsEvent()); -} - -TEST_F(OmahaRequestActionTest, FormatDeltaOkayOutputTest) { - for (int i = 0; i < 2; i++) { - bool delta_okay = i == 1; - const char* delta_okay_str = delta_okay ? "true" : "false"; - brillo::Blob post_data; - - request_params_.set_delta_okay(delta_okay); - - ASSERT_FALSE(TestUpdateCheck("invalid xml>", - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLParseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - // convert post_data to string - string post_str(post_data.begin(), post_data.end()); - EXPECT_NE( - post_str.find(base::StringPrintf(" delta_okay=\"%s\"", delta_okay_str)), - string::npos) - << "i = " << i; - } -} - -TEST_F(OmahaRequestActionTest, FormatInteractiveOutputTest) { - for (int i = 0; i < 2; i++) { - bool interactive = i == 1; - const char* interactive_str = interactive ? "ondemandupdate" : "scheduler"; - brillo::Blob post_data; - FakeSystemState fake_system_state; - - request_params_.set_interactive(interactive); - - ASSERT_FALSE(TestUpdateCheck("invalid xml>", - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLParseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - // convert post_data to string - string post_str(post_data.begin(), post_data.end()); - EXPECT_NE(post_str.find( - base::StringPrintf("installsource=\"%s\"", interactive_str)), - string::npos) - << "i = " << i; - } -} - -TEST_F(OmahaRequestActionTest, FormatTargetVersionPrefixOutputTest) { - for (int i = 0; i < 2; i++) { - bool target_version_set = i == 1; - const char* target_version_prefix = target_version_set ? "10032." : ""; - brillo::Blob post_data; - FakeSystemState fake_system_state; - - request_params_.set_target_version_prefix(target_version_prefix); - - ASSERT_FALSE(TestUpdateCheck("invalid xml>", - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLParseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - // convert post_data to string - string post_str(post_data.begin(), post_data.end()); - if (target_version_set) { - EXPECT_NE(post_str.find("<updatecheck targetversionprefix=\"10032.\">"), - string::npos) - << "i = " << i; - } else { - EXPECT_EQ(post_str.find("targetversionprefix"), string::npos) - << "i = " << i; - } - } -} - -TEST_F(OmahaRequestActionTest, FormatRollbackAllowedOutputTest) { - for (int i = 0; i < 4; i++) { - bool rollback_allowed = i / 2 == 0; - bool target_version_set = i % 2 == 0; - brillo::Blob post_data; - FakeSystemState fake_system_state; - - request_params_.set_target_version_prefix(target_version_set ? "10032." - : ""); - request_params_.set_rollback_allowed(rollback_allowed); - - ASSERT_FALSE(TestUpdateCheck("invalid xml>", - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLParseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - // convert post_data to string - string post_str(post_data.begin(), post_data.end()); - if (rollback_allowed && target_version_set) { - EXPECT_NE(post_str.find("rollback_allowed=\"true\""), string::npos) - << "i = " << i; - } else { - EXPECT_EQ(post_str.find("rollback_allowed"), string::npos) << "i = " << i; - } - } -} - -TEST_F(OmahaRequestActionTest, OmahaEventTest) { - OmahaEvent default_event; - EXPECT_EQ(OmahaEvent::kTypeUnknown, default_event.type); - EXPECT_EQ(OmahaEvent::kResultError, default_event.result); - EXPECT_EQ(ErrorCode::kError, default_event.error_code); - - OmahaEvent success_event(OmahaEvent::kTypeUpdateDownloadStarted); - EXPECT_EQ(OmahaEvent::kTypeUpdateDownloadStarted, success_event.type); - EXPECT_EQ(OmahaEvent::kResultSuccess, success_event.result); - EXPECT_EQ(ErrorCode::kSuccess, success_event.error_code); - - OmahaEvent error_event(OmahaEvent::kTypeUpdateDownloadFinished, - OmahaEvent::kResultError, - ErrorCode::kError); - EXPECT_EQ(OmahaEvent::kTypeUpdateDownloadFinished, error_event.type); - EXPECT_EQ(OmahaEvent::kResultError, error_event.result); - EXPECT_EQ(ErrorCode::kError, error_event.error_code); -} - -void OmahaRequestActionTest::PingTest(bool ping_only) { - NiceMock<MockPrefs> prefs; - fake_system_state_.set_prefs(&prefs); - EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _)) - .Times(AnyNumber()); - EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); - // Add a few hours to the day difference to test no rounding, etc. - int64_t five_days_ago = - (Time::Now() - TimeDelta::FromHours(5 * 24 + 13)).ToInternalValue(); - int64_t six_days_ago = - (Time::Now() - TimeDelta::FromHours(6 * 24 + 11)).ToInternalValue(); - EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _)) - .WillOnce(DoAll(SetArgPointee<1>(0), Return(true))); - EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _)) - .WillOnce(DoAll(SetArgPointee<1>(six_days_ago), Return(true))); - EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _)) - .WillOnce(DoAll(SetArgPointee<1>(five_days_ago), Return(true))); - brillo::Blob post_data; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - ping_only, - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - string post_str(post_data.begin(), post_data.end()); - EXPECT_NE(post_str.find("<ping active=\"1\" a=\"6\" r=\"5\"></ping>"), - string::npos); - if (ping_only) { - EXPECT_EQ(post_str.find("updatecheck"), string::npos); - EXPECT_EQ(post_str.find("previousversion"), string::npos); - } else { - EXPECT_NE(post_str.find("updatecheck"), string::npos); - EXPECT_NE(post_str.find("previousversion"), string::npos); - } -} - -TEST_F(OmahaRequestActionTest, PingTestSendOnlyAPing) { - PingTest(true /* ping_only */); -} - -TEST_F(OmahaRequestActionTest, PingTestSendAlsoAnUpdateCheck) { - PingTest(false /* ping_only */); -} - -TEST_F(OmahaRequestActionTest, ActivePingTest) { - NiceMock<MockPrefs> prefs; - fake_system_state_.set_prefs(&prefs); - EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _)) - .Times(AnyNumber()); - EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); - int64_t three_days_ago = - (Time::Now() - TimeDelta::FromHours(3 * 24 + 12)).ToInternalValue(); - int64_t now = Time::Now().ToInternalValue(); - EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _)) - .WillOnce(DoAll(SetArgPointee<1>(0), Return(true))); - EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _)) - .WillOnce(DoAll(SetArgPointee<1>(three_days_ago), Return(true))); - EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _)) - .WillOnce(DoAll(SetArgPointee<1>(now), Return(true))); - brillo::Blob post_data; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - string post_str(post_data.begin(), post_data.end()); - EXPECT_NE(post_str.find("<ping active=\"1\" a=\"3\"></ping>"), string::npos); -} - -TEST_F(OmahaRequestActionTest, RollCallPingTest) { - NiceMock<MockPrefs> prefs; - fake_system_state_.set_prefs(&prefs); - EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _)) - .Times(AnyNumber()); - EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); - int64_t four_days_ago = - (Time::Now() - TimeDelta::FromHours(4 * 24)).ToInternalValue(); - int64_t now = Time::Now().ToInternalValue(); - EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _)) - .WillOnce(DoAll(SetArgPointee<1>(0), Return(true))); - EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _)) - .WillOnce(DoAll(SetArgPointee<1>(now), Return(true))); - EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _)) - .WillOnce(DoAll(SetArgPointee<1>(four_days_ago), Return(true))); - brillo::Blob post_data; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - string post_str(post_data.begin(), post_data.end()); - EXPECT_NE(post_str.find("<ping active=\"1\" r=\"4\"></ping>\n"), - string::npos); -} - -TEST_F(OmahaRequestActionTest, NoPingTest) { - NiceMock<MockPrefs> prefs; - fake_system_state_.set_prefs(&prefs); - EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _)) - .Times(AnyNumber()); - EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); - int64_t one_hour_ago = - (Time::Now() - TimeDelta::FromHours(1)).ToInternalValue(); - EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _)) - .WillOnce(DoAll(SetArgPointee<1>(0), Return(true))); - EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _)) - .WillOnce(DoAll(SetArgPointee<1>(one_hour_ago), Return(true))); - EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _)) - .WillOnce(DoAll(SetArgPointee<1>(one_hour_ago), Return(true))); - // LastActivePingDay and PrefsLastRollCallPingDay are set even if we didn't - // send a ping. - EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)) - .WillOnce(Return(true)); - EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)) - .WillOnce(Return(true)); - brillo::Blob post_data; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - string post_str(post_data.begin(), post_data.end()); - EXPECT_EQ(post_str.find("ping"), string::npos); -} - -TEST_F(OmahaRequestActionTest, IgnoreEmptyPingTest) { - // This test ensures that we ignore empty ping only requests. - NiceMock<MockPrefs> prefs; - fake_system_state_.set_prefs(&prefs); - int64_t now = Time::Now().ToInternalValue(); - EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _)) - .WillOnce(DoAll(SetArgPointee<1>(now), Return(true))); - EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _)) - .WillOnce(DoAll(SetArgPointee<1>(now), Return(true))); - EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0); - EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0); - brillo::Blob post_data; - EXPECT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - true, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUnset, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - EXPECT_EQ(0U, post_data.size()); -} - -TEST_F(OmahaRequestActionTest, BackInTimePingTest) { - NiceMock<MockPrefs> prefs; - fake_system_state_.set_prefs(&prefs); - EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _)) - .Times(AnyNumber()); - EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); - int64_t future = - (Time::Now() + TimeDelta::FromHours(3 * 24 + 4)).ToInternalValue(); - EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _)) - .WillOnce(DoAll(SetArgPointee<1>(0), Return(true))); - EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _)) - .WillOnce(DoAll(SetArgPointee<1>(future), Return(true))); - EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _)) - .WillOnce(DoAll(SetArgPointee<1>(future), Return(true))); - EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)) - .WillOnce(Return(true)); - EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)) - .WillOnce(Return(true)); - brillo::Blob post_data; - ASSERT_TRUE( - TestUpdateCheck("<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " - "protocol=\"3.0\"><daystart elapsed_seconds=\"100\"/>" - "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>" - "<updatecheck status=\"noupdate\"/></app></response>", - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - string post_str(post_data.begin(), post_data.end()); - EXPECT_EQ(post_str.find("ping"), string::npos); -} - -TEST_F(OmahaRequestActionTest, LastPingDayUpdateTest) { - // This test checks that the action updates the last ping day to now - // minus 200 seconds with a slack of 5 seconds. Therefore, the test - // may fail if it runs for longer than 5 seconds. It shouldn't run - // that long though. - int64_t midnight = - (Time::Now() - TimeDelta::FromSeconds(200)).ToInternalValue(); - int64_t midnight_slack = - (Time::Now() - TimeDelta::FromSeconds(195)).ToInternalValue(); - NiceMock<MockPrefs> prefs; - fake_system_state_.set_prefs(&prefs); - EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber()); - EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); - EXPECT_CALL(prefs, - SetInt64(kPrefsLastActivePingDay, - AllOf(Ge(midnight), Le(midnight_slack)))) - .WillOnce(Return(true)); - EXPECT_CALL(prefs, - SetInt64(kPrefsLastRollCallPingDay, - AllOf(Ge(midnight), Le(midnight_slack)))) - .WillOnce(Return(true)); - ASSERT_TRUE( - TestUpdateCheck("<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " - "protocol=\"3.0\"><daystart elapsed_seconds=\"200\"/>" - "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>" - "<updatecheck status=\"noupdate\"/></app></response>", - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - nullptr)); -} - -TEST_F(OmahaRequestActionTest, NoElapsedSecondsTest) { - NiceMock<MockPrefs> prefs; - fake_system_state_.set_prefs(&prefs); - EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber()); - EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); - EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0); - EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0); - ASSERT_TRUE( - TestUpdateCheck("<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " - "protocol=\"3.0\"><daystart blah=\"200\"/>" - "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>" - "<updatecheck status=\"noupdate\"/></app></response>", - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - nullptr)); -} - -TEST_F(OmahaRequestActionTest, BadElapsedSecondsTest) { - NiceMock<MockPrefs> prefs; - fake_system_state_.set_prefs(&prefs); - EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber()); - EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); - EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0); - EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0); - ASSERT_TRUE( - TestUpdateCheck("<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " - "protocol=\"3.0\"><daystart elapsed_seconds=\"x\"/>" - "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>" - "<updatecheck status=\"noupdate\"/></app></response>", - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - nullptr)); -} - -TEST_F(OmahaRequestActionTest, ParseUpdateCheckAttributesTest) { - // Test that the "eol" flags is only parsed from the "_eol" attribute and not - // the "eol" attribute. - ASSERT_TRUE( - TestUpdateCheck("<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " - "protocol=\"3.0\"><app appid=\"foo\" status=\"ok\">" - "<ping status=\"ok\"/><updatecheck status=\"noupdate\" " - "_eol=\"security-only\" eol=\"eol\" _foo=\"bar\"/>" - "</app></response>", - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - nullptr)); - string eol_pref; - EXPECT_TRUE( - fake_system_state_.prefs()->GetString(kPrefsOmahaEolStatus, &eol_pref)); - // Note that the eol="eol" attribute should be ignored and the _eol should be - // used instead. - EXPECT_EQ("security-only", eol_pref); -} - -TEST_F(OmahaRequestActionTest, NoUniqueIDTest) { - brillo::Blob post_data; - ASSERT_FALSE(TestUpdateCheck("invalid xml>", - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLParseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, // response - &post_data)); - // convert post_data to string - string post_str(post_data.begin(), post_data.end()); - EXPECT_EQ(post_str.find("machineid="), string::npos); - EXPECT_EQ(post_str.find("userid="), string::npos); -} - -TEST_F(OmahaRequestActionTest, NetworkFailureTest) { - OmahaResponse response; - const int http_error_code = - static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 501; - ASSERT_FALSE(TestUpdateCheck("", - 501, - false, // ping_only - static_cast<ErrorCode>(http_error_code), - metrics::CheckResult::kDownloadError, - metrics::CheckReaction::kUnset, - static_cast<metrics::DownloadErrorCode>(501), - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, NetworkFailureBadHTTPCodeTest) { - OmahaResponse response; - const int http_error_code = - static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 999; - ASSERT_FALSE(TestUpdateCheck("", - 1500, - false, // ping_only - static_cast<ErrorCode>(http_error_code), - metrics::CheckResult::kDownloadError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kHttpStatusOther, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsPersistedFirstTime) { - OmahaResponse response; - request_params_.set_wall_clock_based_wait_enabled(true); - request_params_.set_waiting_period(TimeDelta().FromDays(1)); - request_params_.set_update_check_count_wait_enabled(false); - - Time arbitrary_date; - ASSERT_TRUE(Time::FromString("6/4/1989", &arbitrary_date)); - fake_system_state_.fake_clock()->SetWallclockTime(arbitrary_date); - ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kOmahaUpdateDeferredPerPolicy, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kDeferring, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - - int64_t timestamp = 0; - ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateFirstSeenAt, ×tamp)); - EXPECT_EQ(arbitrary_date.ToInternalValue(), timestamp); - EXPECT_FALSE(response.update_exists); - - // Verify if we are interactive check we don't defer. - request_params_.set_interactive(true); - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); -} - -TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsUsedIfAlreadyPresent) { - OmahaResponse response; - request_params_.set_wall_clock_based_wait_enabled(true); - request_params_.set_waiting_period(TimeDelta().FromDays(1)); - request_params_.set_update_check_count_wait_enabled(false); - - Time t1, t2; - ASSERT_TRUE(Time::FromString("1/1/2012", &t1)); - ASSERT_TRUE(Time::FromString("1/3/2012", &t2)); - ASSERT_TRUE( - fake_prefs_.SetInt64(kPrefsUpdateFirstSeenAt, t1.ToInternalValue())); - fake_system_state_.fake_clock()->SetWallclockTime(t2); - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - - EXPECT_TRUE(response.update_exists); - - // Make sure the timestamp t1 is unchanged showing that it was reused. - int64_t timestamp = 0; - ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateFirstSeenAt, ×tamp)); - ASSERT_TRUE(timestamp == t1.ToInternalValue()); -} - -TEST_F(OmahaRequestActionTest, TestChangingToMoreStableChannel) { - // Create a uniquely named test directory. - base::ScopedTempDir tempdir; - ASSERT_TRUE(tempdir.CreateUniqueTempDir()); - - brillo::Blob post_data; - request_params_.set_root(tempdir.GetPath().value()); - request_params_.set_app_id("{22222222-2222-2222-2222-222222222222}"); - request_params_.set_app_version("1.2.3.4"); - request_params_.set_product_components("o.bundle=1"); - request_params_.set_current_channel("canary-channel"); - EXPECT_TRUE( - request_params_.SetTargetChannel("stable-channel", true, nullptr)); - request_params_.UpdateDownloadChannel(); - EXPECT_TRUE(request_params_.ShouldPowerwash()); - ASSERT_FALSE(TestUpdateCheck("invalid xml>", - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLParseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, // response - &post_data)); - // convert post_data to string - string post_str(post_data.begin(), post_data.end()); - EXPECT_NE( - string::npos, - post_str.find("appid=\"{22222222-2222-2222-2222-222222222222}\" " - "version=\"0.0.0.0\" from_version=\"1.2.3.4\" " - "track=\"stable-channel\" from_track=\"canary-channel\" ")); - EXPECT_EQ(string::npos, post_str.find("o.bundle")); -} - -TEST_F(OmahaRequestActionTest, TestChangingToLessStableChannel) { - // Create a uniquely named test directory. - base::ScopedTempDir tempdir; - ASSERT_TRUE(tempdir.CreateUniqueTempDir()); - - brillo::Blob post_data; - request_params_.set_root(tempdir.GetPath().value()); - request_params_.set_app_id("{11111111-1111-1111-1111-111111111111}"); - request_params_.set_app_version("5.6.7.8"); - request_params_.set_product_components("o.bundle=1"); - request_params_.set_current_channel("stable-channel"); - EXPECT_TRUE( - request_params_.SetTargetChannel("canary-channel", false, nullptr)); - request_params_.UpdateDownloadChannel(); - EXPECT_FALSE(request_params_.ShouldPowerwash()); - ASSERT_FALSE(TestUpdateCheck("invalid xml>", - -1, - false, // ping_only - ErrorCode::kOmahaRequestXMLParseError, - metrics::CheckResult::kParsingError, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, // response - &post_data)); - // Convert post_data to string. - string post_str(post_data.begin(), post_data.end()); - EXPECT_NE( - string::npos, - post_str.find("appid=\"{11111111-1111-1111-1111-111111111111}\" " - "version=\"5.6.7.8\" " - "track=\"canary-channel\" from_track=\"stable-channel\"")); - EXPECT_EQ(string::npos, post_str.find("from_version")); - EXPECT_NE(string::npos, post_str.find("o.bundle.version=\"1\"")); -} - -// Checks that the initial ping with a=-1 r=-1 is not send when the device -// was powerwashed. -TEST_F(OmahaRequestActionTest, PingWhenPowerwashed) { - fake_prefs_.SetString(kPrefsPreviousVersion, ""); - - // Flag that the device was powerwashed in the past. - fake_system_state_.fake_hardware()->SetPowerwashCount(1); - - brillo::Blob post_data; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - // We shouldn't send a ping in this case since powerwash > 0. - string post_str(post_data.begin(), post_data.end()); - EXPECT_EQ(string::npos, post_str.find("<ping")); -} - -// Checks that the initial ping with a=-1 r=-1 is not send when the device -// first_active_omaha_ping_sent is set. -TEST_F(OmahaRequestActionTest, PingWhenFirstActiveOmahaPingIsSent) { - fake_prefs_.SetString(kPrefsPreviousVersion, ""); - - // Flag that the device was not powerwashed in the past. - fake_system_state_.fake_hardware()->SetPowerwashCount(0); - - // Flag that the device has sent first active ping in the past. - fake_system_state_.fake_hardware()->SetFirstActiveOmahaPingSent(); - - brillo::Blob post_data; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - // We shouldn't send a ping in this case since - // first_active_omaha_ping_sent=true - string post_str(post_data.begin(), post_data.end()); - EXPECT_EQ(string::npos, post_str.find("<ping")); -} - -// Checks that the event 54 is sent on a reboot to a new update. -TEST_F(OmahaRequestActionTest, RebootAfterUpdateEvent) { - // Flag that the device was updated in a previous boot. - fake_prefs_.SetString(kPrefsPreviousVersion, "1.2.3.4"); - - brillo::Blob post_data; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - nullptr, - &post_data)); - string post_str(post_data.begin(), post_data.end()); - - // An event 54 is included and has the right version. - EXPECT_NE( - string::npos, - post_str.find(base::StringPrintf("<event eventtype=\"%d\"", - OmahaEvent::kTypeRebootedAfterUpdate))); - EXPECT_NE(string::npos, - post_str.find("previousversion=\"1.2.3.4\"></event>")); - - // The previous version flag should have been removed. - EXPECT_TRUE(fake_prefs_.Exists(kPrefsPreviousVersion)); - string prev_version; - EXPECT_TRUE(fake_prefs_.GetString(kPrefsPreviousVersion, &prev_version)); - EXPECT_TRUE(prev_version.empty()); -} - -void OmahaRequestActionTest::P2PTest(bool initial_allow_p2p_for_downloading, - bool initial_allow_p2p_for_sharing, - bool omaha_disable_p2p_for_downloading, - bool omaha_disable_p2p_for_sharing, - bool payload_state_allow_p2p_attempt, - bool expect_p2p_client_lookup, - const string& p2p_client_result_url, - bool expected_allow_p2p_for_downloading, - bool expected_allow_p2p_for_sharing, - const string& expected_p2p_url) { - OmahaResponse response; - bool actual_allow_p2p_for_downloading = initial_allow_p2p_for_downloading; - bool actual_allow_p2p_for_sharing = initial_allow_p2p_for_sharing; - string actual_p2p_url; - - MockPayloadState mock_payload_state; - fake_system_state_.set_payload_state(&mock_payload_state); - EXPECT_CALL(mock_payload_state, P2PAttemptAllowed()) - .WillRepeatedly(Return(payload_state_allow_p2p_attempt)); - EXPECT_CALL(mock_payload_state, GetUsingP2PForDownloading()) - .WillRepeatedly(ReturnPointee(&actual_allow_p2p_for_downloading)); - EXPECT_CALL(mock_payload_state, GetUsingP2PForSharing()) - .WillRepeatedly(ReturnPointee(&actual_allow_p2p_for_sharing)); - EXPECT_CALL(mock_payload_state, SetUsingP2PForDownloading(_)) - .WillRepeatedly(SaveArg<0>(&actual_allow_p2p_for_downloading)); - EXPECT_CALL(mock_payload_state, SetUsingP2PForSharing(_)) - .WillRepeatedly(SaveArg<0>(&actual_allow_p2p_for_sharing)); - EXPECT_CALL(mock_payload_state, SetP2PUrl(_)) - .WillRepeatedly(SaveArg<0>(&actual_p2p_url)); - - MockP2PManager mock_p2p_manager; - fake_system_state_.set_p2p_manager(&mock_p2p_manager); - mock_p2p_manager.fake().SetLookupUrlForFileResult(p2p_client_result_url); - - TimeDelta timeout = TimeDelta::FromSeconds(kMaxP2PNetworkWaitTimeSeconds); - EXPECT_CALL(mock_p2p_manager, LookupUrlForFile(_, _, timeout, _)) - .Times(expect_p2p_client_lookup ? 1 : 0); - - fake_update_response_.disable_p2p_for_downloading = - omaha_disable_p2p_for_downloading; - fake_update_response_.disable_p2p_for_sharing = omaha_disable_p2p_for_sharing; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); - - EXPECT_EQ(omaha_disable_p2p_for_downloading, - response.disable_p2p_for_downloading); - EXPECT_EQ(omaha_disable_p2p_for_sharing, response.disable_p2p_for_sharing); - - EXPECT_EQ(expected_allow_p2p_for_downloading, - actual_allow_p2p_for_downloading); - EXPECT_EQ(expected_allow_p2p_for_sharing, actual_allow_p2p_for_sharing); - EXPECT_EQ(expected_p2p_url, actual_p2p_url); -} - -TEST_F(OmahaRequestActionTest, P2PWithPeer) { - P2PTest(true, // initial_allow_p2p_for_downloading - true, // initial_allow_p2p_for_sharing - false, // omaha_disable_p2p_for_downloading - false, // omaha_disable_p2p_for_sharing - true, // payload_state_allow_p2p_attempt - true, // expect_p2p_client_lookup - "http://1.3.5.7/p2p", // p2p_client_result_url - true, // expected_allow_p2p_for_downloading - true, // expected_allow_p2p_for_sharing - "http://1.3.5.7/p2p"); // expected_p2p_url -} - -TEST_F(OmahaRequestActionTest, P2PWithoutPeer) { - P2PTest(true, // initial_allow_p2p_for_downloading - true, // initial_allow_p2p_for_sharing - false, // omaha_disable_p2p_for_downloading - false, // omaha_disable_p2p_for_sharing - true, // payload_state_allow_p2p_attempt - true, // expect_p2p_client_lookup - "", // p2p_client_result_url - false, // expected_allow_p2p_for_downloading - true, // expected_allow_p2p_for_sharing - ""); // expected_p2p_url -} - -TEST_F(OmahaRequestActionTest, P2PDownloadNotAllowed) { - P2PTest(false, // initial_allow_p2p_for_downloading - true, // initial_allow_p2p_for_sharing - false, // omaha_disable_p2p_for_downloading - false, // omaha_disable_p2p_for_sharing - true, // payload_state_allow_p2p_attempt - false, // expect_p2p_client_lookup - "unset", // p2p_client_result_url - false, // expected_allow_p2p_for_downloading - true, // expected_allow_p2p_for_sharing - ""); // expected_p2p_url -} - -TEST_F(OmahaRequestActionTest, P2PWithPeerDownloadDisabledByOmaha) { - P2PTest(true, // initial_allow_p2p_for_downloading - true, // initial_allow_p2p_for_sharing - true, // omaha_disable_p2p_for_downloading - false, // omaha_disable_p2p_for_sharing - true, // payload_state_allow_p2p_attempt - false, // expect_p2p_client_lookup - "unset", // p2p_client_result_url - false, // expected_allow_p2p_for_downloading - true, // expected_allow_p2p_for_sharing - ""); // expected_p2p_url -} - -TEST_F(OmahaRequestActionTest, P2PWithPeerSharingDisabledByOmaha) { - P2PTest(true, // initial_allow_p2p_for_downloading - true, // initial_allow_p2p_for_sharing - false, // omaha_disable_p2p_for_downloading - true, // omaha_disable_p2p_for_sharing - true, // payload_state_allow_p2p_attempt - true, // expect_p2p_client_lookup - "http://1.3.5.7/p2p", // p2p_client_result_url - true, // expected_allow_p2p_for_downloading - false, // expected_allow_p2p_for_sharing - "http://1.3.5.7/p2p"); // expected_p2p_url -} - -TEST_F(OmahaRequestActionTest, P2PWithPeerBothDisabledByOmaha) { - P2PTest(true, // initial_allow_p2p_for_downloading - true, // initial_allow_p2p_for_sharing - true, // omaha_disable_p2p_for_downloading - true, // omaha_disable_p2p_for_sharing - true, // payload_state_allow_p2p_attempt - false, // expect_p2p_client_lookup - "unset", // p2p_client_result_url - false, // expected_allow_p2p_for_downloading - false, // expected_allow_p2p_for_sharing - ""); // expected_p2p_url -} - -bool OmahaRequestActionTest::InstallDateParseHelper(const string& elapsed_days, - OmahaResponse* response) { - fake_update_response_.elapsed_days = elapsed_days; - return TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - response, - nullptr); -} - -TEST_F(OmahaRequestActionTest, ParseInstallDateFromResponse) { - OmahaResponse response; - - // Simulate a successful update check that happens during OOBE. The - // deadline in the response is needed to force the update attempt to - // occur; responses without a deadline seen during OOBE will normally - // return ErrorCode::kNonCriticalUpdateInOOBE. - fake_system_state_.fake_hardware()->UnsetIsOOBEComplete(); - fake_update_response_.deadline = "20101020"; - - // Check that we parse elapsed_days in the Omaha Response correctly. - // and that the kPrefsInstallDateDays value is written to. - EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays)); - EXPECT_TRUE(InstallDateParseHelper("42", &response)); - EXPECT_TRUE(response.update_exists); - EXPECT_EQ(42, response.install_date_days); - EXPECT_TRUE(fake_prefs_.Exists(kPrefsInstallDateDays)); - int64_t prefs_days; - EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days)); - EXPECT_EQ(prefs_days, 42); - - // If there already is a value set, we shouldn't do anything. - EXPECT_TRUE(InstallDateParseHelper("7", &response)); - EXPECT_TRUE(response.update_exists); - EXPECT_EQ(7, response.install_date_days); - EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days)); - EXPECT_EQ(prefs_days, 42); - - // Note that elapsed_days is not necessarily divisible by 7 so check - // that we round down correctly when populating kPrefsInstallDateDays. - EXPECT_TRUE(fake_prefs_.Delete(kPrefsInstallDateDays)); - EXPECT_TRUE(InstallDateParseHelper("23", &response)); - EXPECT_TRUE(response.update_exists); - EXPECT_EQ(23, response.install_date_days); - EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days)); - EXPECT_EQ(prefs_days, 21); - - // Check that we correctly handle elapsed_days not being included in - // the Omaha Response. - EXPECT_TRUE(InstallDateParseHelper("", &response)); - EXPECT_TRUE(response.update_exists); - EXPECT_EQ(-1, response.install_date_days); -} - -// If there is no prefs and OOBE is not complete, we should not -// report anything to Omaha. -TEST_F(OmahaRequestActionTest, GetInstallDateWhenNoPrefsNorOOBE) { - fake_system_state_.fake_hardware()->UnsetIsOOBEComplete(); - EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), -1); - EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays)); -} - -// If OOBE is complete and happened on a valid date (e.g. after Jan -// 1 2007 0:00 PST), that date should be used and written to -// prefs. However, first try with an invalid date and check we do -// nothing. -TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedWithInvalidDate) { - Time oobe_date = Time::FromTimeT(42); // Dec 31, 1969 16:00:42 PST. - fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date); - EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), -1); - EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays)); -} - -// Then check with a valid date. The date Jan 20, 2007 0:00 PST -// should yield an InstallDate of 14. -TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedWithValidDate) { - Time oobe_date = Time::FromTimeT(1169280000); // Jan 20, 2007 0:00 PST. - fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date); - EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 14); - EXPECT_TRUE(fake_prefs_.Exists(kPrefsInstallDateDays)); - - int64_t prefs_days; - EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days)); - EXPECT_EQ(prefs_days, 14); -} - -// Now that we have a valid date in prefs, check that we keep using -// that even if OOBE date reports something else. The date Jan 30, -// 2007 0:00 PST should yield an InstallDate of 28... but since -// there's a prefs file, we should still get 14. -TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedDateChanges) { - // Set a valid date in the prefs first. - EXPECT_TRUE(fake_prefs_.SetInt64(kPrefsInstallDateDays, 14)); - - Time oobe_date = Time::FromTimeT(1170144000); // Jan 30, 2007 0:00 PST. - fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date); - EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 14); - - int64_t prefs_days; - EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days)); - EXPECT_EQ(prefs_days, 14); - - // If we delete the prefs file, we should get 28 days. - EXPECT_TRUE(fake_prefs_.Delete(kPrefsInstallDateDays)); - EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 28); - EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days)); - EXPECT_EQ(prefs_days, 28); -} - -// Verifies that a device with no device policy, and is not a consumer -// device sets the max kernel key version to the current version. -// ie. the same behavior as if rollback is enabled. -TEST_F(OmahaRequestActionTest, NoPolicyEnterpriseDevicesSetMaxRollback) { - FakeHardware* fake_hw = fake_system_state_.fake_hardware(); - - // Setup and verify some initial default values for the kernel TPM - // values that control verified boot and rollback. - const int min_kernel_version = 4; - fake_hw->SetMinKernelKeyVersion(min_kernel_version); - fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity); - EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); - EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward()); - - EXPECT_CALL( - *fake_system_state_.mock_metrics_reporter(), - ReportKeyVersionMetrics(min_kernel_version, min_kernel_version, true)) - .Times(1); - - OmahaResponse response; - TestRollbackCheck(false /* is_consumer_device */, - 3 /* rollback_allowed_milestones */, - false /* is_policy_loaded */, - &response); - - // Verify kernel_max_rollforward was set to the current minimum - // kernel key version. This has the effect of freezing roll - // forwards indefinitely. This will hold the rollback window - // open until a future change will be able to move this forward - // relative the configured window. - EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); - EXPECT_EQ(min_kernel_version, fake_hw->GetMaxKernelKeyRollforward()); -} - -// Verifies that a conmsumer device with no device policy sets the -// max kernel key version to the current version. ie. the same -// behavior as if rollback is enabled. -TEST_F(OmahaRequestActionTest, NoPolicyConsumerDevicesSetMaxRollback) { - FakeHardware* fake_hw = fake_system_state_.fake_hardware(); - - // Setup and verify some initial default values for the kernel TPM - // values that control verified boot and rollback. - const int min_kernel_version = 3; - fake_hw->SetMinKernelKeyVersion(min_kernel_version); - fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity); - EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); - EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward()); - - EXPECT_CALL( - *fake_system_state_.mock_metrics_reporter(), - ReportKeyVersionMetrics(min_kernel_version, kRollforwardInfinity, true)) - .Times(1); - - OmahaResponse response; - TestRollbackCheck(true /* is_consumer_device */, - 3 /* rollback_allowed_milestones */, - false /* is_policy_loaded */, - &response); - - // Verify that with rollback disabled that kernel_max_rollforward - // was set to logical infinity. This is the expected behavior for - // consumer devices and matches the existing behavior prior to the - // rollback features. - EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); - EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward()); -} - -// Verifies that a device with rollback enabled sets kernel_max_rollforward -// in the TPM to prevent roll forward. -TEST_F(OmahaRequestActionTest, RollbackEnabledDevicesSetMaxRollback) { - FakeHardware* fake_hw = fake_system_state_.fake_hardware(); - - // Setup and verify some initial default values for the kernel TPM - // values that control verified boot and rollback. - const int allowed_milestones = 4; - const int min_kernel_version = 3; - fake_hw->SetMinKernelKeyVersion(min_kernel_version); - fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity); - EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); - EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward()); - - EXPECT_CALL( - *fake_system_state_.mock_metrics_reporter(), - ReportKeyVersionMetrics(min_kernel_version, min_kernel_version, true)) - .Times(1); - - OmahaResponse response; - TestRollbackCheck(false /* is_consumer_device */, - allowed_milestones, - true /* is_policy_loaded */, - &response); - - // Verify that with rollback enabled that kernel_max_rollforward - // was set to the current minimum kernel key version. This has - // the effect of freezing roll forwards indefinitely. This will - // hold the rollback window open until a future change will - // be able to move this forward relative the configured window. - EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); - EXPECT_EQ(min_kernel_version, fake_hw->GetMaxKernelKeyRollforward()); -} - -// Verifies that a device with rollback disabled sets kernel_max_rollforward -// in the TPM to logical infinity, to allow roll forward. -TEST_F(OmahaRequestActionTest, RollbackDisabledDevicesSetMaxRollback) { - FakeHardware* fake_hw = fake_system_state_.fake_hardware(); - - // Setup and verify some initial default values for the kernel TPM - // values that control verified boot and rollback. - const int allowed_milestones = 0; - const int min_kernel_version = 3; - fake_hw->SetMinKernelKeyVersion(min_kernel_version); - fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity); - EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); - EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward()); - - EXPECT_CALL( - *fake_system_state_.mock_metrics_reporter(), - ReportKeyVersionMetrics(min_kernel_version, kRollforwardInfinity, true)) - .Times(1); - - OmahaResponse response; - TestRollbackCheck(false /* is_consumer_device */, - allowed_milestones, - true /* is_policy_loaded */, - &response); - - // Verify that with rollback disabled that kernel_max_rollforward - // was set to logical infinity. - EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion()); - EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward()); -} - -TEST_F(OmahaRequestActionTest, RollbackResponseParsedNoEntries) { - OmahaResponse response; - fake_update_response_.rollback = true; - TestRollbackCheck(false /* is_consumer_device */, - 4 /* rollback_allowed_milestones */, - true /* is_policy_loaded */, - &response); - EXPECT_TRUE(response.is_rollback); -} - -TEST_F(OmahaRequestActionTest, RollbackResponseValidVersionsParsed) { - OmahaResponse response; - fake_update_response_.rollback_firmware_version = "1.2"; - fake_update_response_.rollback_kernel_version = "3.4"; - fake_update_response_.rollback = true; - TestRollbackCheck(false /* is_consumer_device */, - 4 /* rollback_allowed_milestones */, - true /* is_policy_loaded */, - &response); - EXPECT_TRUE(response.is_rollback); - EXPECT_EQ(1, response.rollback_key_version.firmware_key); - EXPECT_EQ(2, response.rollback_key_version.firmware); - EXPECT_EQ(3, response.rollback_key_version.kernel_key); - EXPECT_EQ(4, response.rollback_key_version.kernel); -} - -TEST_F(OmahaRequestActionTest, - TestUpdateFirstSeenAtPrefPersistedIfUpdateExists) { - FakeClock fake_clock; - Time now = Time::Now(); - fake_clock.SetWallclockTime(now); - fake_system_state_.set_clock(&fake_clock); - - OmahaResponse response; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); - EXPECT_TRUE(fake_prefs_.Exists(kPrefsUpdateFirstSeenAt)); - - int64_t stored_first_seen_at_time; - EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateFirstSeenAt, - &stored_first_seen_at_time)); - EXPECT_EQ(now.ToInternalValue(), stored_first_seen_at_time); -} - -TEST_F(OmahaRequestActionTest, - TestUpdateFirstSeenAtPrefNotPersistedIfUpdateFails) { - FakeClock fake_clock; - Time now = Time::Now(); - fake_clock.SetWallclockTime(now); - fake_system_state_.set_clock(&fake_clock); - - OmahaResponse response; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kNoUpdateAvailable, - metrics::CheckReaction::kUnset, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_FALSE(response.update_exists); - EXPECT_FALSE(fake_prefs_.Exists(kPrefsUpdateFirstSeenAt)); -} - -TEST_F(OmahaRequestActionTest, InstallTest) { - OmahaResponse response; - request_params_.set_is_install(true); - request_params_.set_dlc_module_ids({"dlc_no_0", "dlc_no_1"}); - brillo::Blob post_data; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - true, // is_consumer_device - 0, // rollback_allowed_milestones - false, // is_policy_loaded - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - &post_data)); - // Convert post_data to string. - string post_str(post_data.begin(), post_data.end()); - for (const auto& dlc_module_id : request_params_.dlc_module_ids()) { - EXPECT_NE(string::npos, - post_str.find("appid=\"" + fake_update_response_.app_id + "_" + - dlc_module_id + "\"")); - } - EXPECT_NE(string::npos, - post_str.find("appid=\"" + fake_update_response_.app_id + "\"")); - - // Count number of updatecheck tag in response. - int updatecheck_count = 0; - size_t pos = 0; - while ((pos = post_str.find("<updatecheck", pos)) != string::npos) { - updatecheck_count++; - pos++; - } - EXPECT_EQ(request_params_.dlc_module_ids().size(), updatecheck_count); -} - -TEST_F(OmahaRequestActionTest, InstallMissingPlatformVersionTest) { - fake_update_response_.multi_app_skip_updatecheck = true; - fake_update_response_.multi_app_no_update = false; - request_params_.set_is_install(true); - request_params_.set_dlc_module_ids({"dlc_no_0", "dlc_no_1"}); - request_params_.set_app_id(fake_update_response_.app_id_skip_updatecheck); - OmahaResponse response; - ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(), - -1, - false, // ping_only - ErrorCode::kSuccess, - metrics::CheckResult::kUpdateAvailable, - metrics::CheckReaction::kUpdating, - metrics::DownloadErrorCode::kUnset, - &response, - nullptr)); - EXPECT_TRUE(response.update_exists); - EXPECT_EQ(fake_update_response_.current_version, response.version); -} - -} // namespace chromeos_update_engine diff --git a/omaha_utils.cc b/omaha_utils.cc deleted file mode 100644 index 6bd75250..00000000 --- a/omaha_utils.cc +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (C) 2016 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/omaha_utils.h" - -#include <base/logging.h> - -namespace chromeos_update_engine { - -namespace { - -// The possible string values for the end-of-life status. -const char kEolStatusSupported[] = "supported"; -const char kEolStatusSecurityOnly[] = "security-only"; -const char kEolStatusEol[] = "eol"; - -} // namespace - -const char* EolStatusToString(EolStatus eol_status) { - switch (eol_status) { - case EolStatus::kSupported: - return kEolStatusSupported; - case EolStatus::kSecurityOnly: - return kEolStatusSecurityOnly; - case EolStatus::kEol: - return kEolStatusEol; - } - // Only reached if an invalid number is casted to |EolStatus|. - LOG(WARNING) << "Invalid EolStatus value: " << static_cast<int>(eol_status); - return kEolStatusSupported; -} - -EolStatus StringToEolStatus(const std::string& eol_status) { - if (eol_status == kEolStatusSupported || eol_status.empty()) - return EolStatus::kSupported; - if (eol_status == kEolStatusSecurityOnly) - return EolStatus::kSecurityOnly; - if (eol_status == kEolStatusEol) - return EolStatus::kEol; - LOG(WARNING) << "Invalid end-of-life attribute: " << eol_status; - return EolStatus::kSupported; -} - -} // namespace chromeos_update_engine diff --git a/parcelable_update_engine_status.cc b/parcelable_update_engine_status.cc deleted file mode 100644 index 8a2dbeba..00000000 --- a/parcelable_update_engine_status.cc +++ /dev/null @@ -1,122 +0,0 @@ -// -// Copyright (C) 2016 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/parcelable_update_engine_status.h" -#include "update_engine/update_status_utils.h" - -#include <binder/Parcel.h> - -using update_engine::UpdateEngineStatus; - -namespace android { -namespace brillo { - -ParcelableUpdateEngineStatus::ParcelableUpdateEngineStatus( - const UpdateEngineStatus& status) - : last_checked_time_(status.last_checked_time), - current_operation_( - chromeos_update_engine::UpdateStatusToString(status.status)), - progress_(status.progress), - current_version_(String16{status.current_version.c_str()}), - current_system_version_(String16{status.current_system_version.c_str()}), - new_size_(status.new_size_bytes), - new_version_(String16{status.new_version.c_str()}), - new_system_version_(String16{status.new_system_version.c_str()}) {} - -status_t ParcelableUpdateEngineStatus::writeToParcel(Parcel* parcel) const { - status_t status; - - status = parcel->writeInt64(last_checked_time_); - if (status != OK) { - return status; - } - - status = parcel->writeString16(current_operation_); - if (status != OK) { - return status; - } - - status = parcel->writeDouble(progress_); - if (status != OK) { - return status; - } - - status = parcel->writeString16(current_version_); - if (status != OK) { - return status; - } - - status = parcel->writeString16(current_system_version_); - if (status != OK) { - return status; - } - - status = parcel->writeInt64(new_size_); - if (status != OK) { - return status; - } - - status = parcel->writeString16(new_version_); - if (status != OK) { - return status; - } - - return parcel->writeString16(new_system_version_); -} - -status_t ParcelableUpdateEngineStatus::readFromParcel(const Parcel* parcel) { - status_t status; - - status = parcel->readInt64(&last_checked_time_); - if (status != OK) { - return status; - } - - status = parcel->readString16(¤t_operation_); - if (status != OK) { - return status; - } - - status = parcel->readDouble(&progress_); - if (status != OK) { - return status; - } - - status = parcel->readString16(¤t_version_); - if (status != OK) { - return status; - } - - status = parcel->readString16(¤t_system_version_); - if (status != OK) { - return status; - } - - status = parcel->readInt64(&new_size_); - if (status != OK) { - return status; - } - - status = parcel->readString16(&new_version_); - if (status != OK) { - return status; - } - - return parcel->readString16(&new_system_version_); -} - -} // namespace brillo -} // namespace android diff --git a/parcelable_update_engine_status.h b/parcelable_update_engine_status.h deleted file mode 100644 index 3feac769..00000000 --- a/parcelable_update_engine_status.h +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (C) 2016 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_PARCELABLE_UPDATE_ENGINE_STATUS_H_ -#define UPDATE_ENGINE_PARCELABLE_UPDATE_ENGINE_STATUS_H_ - -#include <binder/Parcelable.h> -#include <utils/String16.h> - -#include "update_engine/client_library/include/update_engine/update_status.h" - -namespace android { -namespace brillo { - -// Parcelable object containing the current status of update engine, to be sent -// over binder to clients from the server. -class ParcelableUpdateEngineStatus : public Parcelable { - public: - ParcelableUpdateEngineStatus() = default; - explicit ParcelableUpdateEngineStatus( - const update_engine::UpdateEngineStatus& status); - virtual ~ParcelableUpdateEngineStatus() = default; - - status_t writeToParcel(Parcel* parcel) const override; - status_t readFromParcel(const Parcel* parcel) override; - - // This list is kept in the Parcelable serialization order. - - // When the update_engine last checked for updates (seconds since unix Epoch) - int64_t last_checked_time_; - // The current status/operation of the update_engine. - android::String16 current_operation_; - // The current progress (0.0f-1.0f). - double progress_; - // The current product version. - android::String16 current_version_; - // The current system version. - android::String16 current_system_version_; - // The size of the update (bytes). This is int64_t for java compatibility. - int64_t new_size_; - // The new product version. - android::String16 new_version_; - // The new system version, if there is one (empty, otherwise). - android::String16 new_system_version_; -}; - -} // namespace brillo -} // namespace android - -#endif // UPDATE_ENGINE_PARCELABLE_UPDATE_ENGINE_STATUS_H_ diff --git a/parcelable_update_engine_status_unittest.cc b/parcelable_update_engine_status_unittest.cc deleted file mode 100644 index 20decb6b..00000000 --- a/parcelable_update_engine_status_unittest.cc +++ /dev/null @@ -1,92 +0,0 @@ -// -// Copyright (C) 2017 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/parcelable_update_engine_status.h" -#include "update_engine/update_status_utils.h" - -#include <binder/Parcel.h> -#include <gtest/gtest.h> - -using android::Parcel; -using android::status_t; -using android::String16; -using android::brillo::ParcelableUpdateEngineStatus; -using update_engine::UpdateEngineStatus; -using update_engine::UpdateStatus; - -TEST(ParcelableUpdateEngineStatusTest, TestCreationFromUpdateEngineStatus) { - // This test creates an object and verifies that all the UpdateEngineStatus - // values are properly reflected in the Parcelable version of the class. - - UpdateEngineStatus ue_status = {123456789, - UpdateStatus::DOWNLOADING, - "0.1.2.3", - "1.2.3.4", - 0.5f, - 34567, - "2.3.4.5", - "3.4.5.6"}; - ParcelableUpdateEngineStatus parcelable_status(ue_status); - EXPECT_EQ(ue_status.last_checked_time, parcelable_status.last_checked_time_); - EXPECT_EQ( - String16{chromeos_update_engine::UpdateStatusToString(ue_status.status)}, - parcelable_status.current_operation_); - EXPECT_EQ(String16{ue_status.current_version.c_str()}, - parcelable_status.current_version_); - EXPECT_EQ(String16{ue_status.current_system_version.c_str()}, - parcelable_status.current_system_version_); - EXPECT_EQ(ue_status.progress, parcelable_status.progress_); - EXPECT_EQ(static_cast<int64_t>(ue_status.new_size_bytes), - parcelable_status.new_size_); - EXPECT_EQ(String16{ue_status.new_version.c_str()}, - parcelable_status.new_version_); - EXPECT_EQ(String16{ue_status.new_system_version.c_str()}, - parcelable_status.new_system_version_); -} - -TEST(ParcelableUpdateEngineStatusTest, TestParceling) { - // This tests the writeToParcel and readFromParcel methods for being correctly - // matched. - UpdateEngineStatus ue_status = {123456789, - UpdateStatus::DOWNLOADING, - "0.1.2.3", - "1.2.3.4", - 0.5f, - 34567, - "2.3.4.5", - "3.4.5.6"}; - ParcelableUpdateEngineStatus source_status(ue_status); - Parcel parcel_source, parcel_target; - status_t status = source_status.writeToParcel(&parcel_source); - EXPECT_EQ(::android::OK, status); - size_t parcel_len = parcel_source.dataSize(); - status = parcel_target.setData(parcel_source.data(), parcel_len); - EXPECT_EQ(::android::OK, status); - ParcelableUpdateEngineStatus target_status; - status = target_status.readFromParcel(&parcel_target); - EXPECT_EQ(::android::OK, status); - - EXPECT_EQ(source_status.last_checked_time_, target_status.last_checked_time_); - EXPECT_EQ(source_status.current_operation_, target_status.current_operation_); - EXPECT_EQ(source_status.current_version_, target_status.current_version_); - EXPECT_EQ(source_status.current_system_version_, - target_status.current_system_version_); - EXPECT_EQ(source_status.progress_, target_status.progress_); - EXPECT_EQ(source_status.new_size_, target_status.new_size_); - EXPECT_EQ(source_status.new_version_, target_status.new_version_); - EXPECT_EQ(source_status.new_system_version_, - target_status.new_system_version_); -} diff --git a/payload_consumer/bzip_extent_writer.cc b/payload_consumer/bzip_extent_writer.cc index 0c25c71f..26fdc5f4 100644 --- a/payload_consumer/bzip_extent_writer.cc +++ b/payload_consumer/bzip_extent_writer.cc @@ -29,8 +29,7 @@ BzipExtentWriter::~BzipExtentWriter() { TEST_AND_RETURN(input_buffer_.empty()); } -bool BzipExtentWriter::Init(FileDescriptorPtr fd, - const RepeatedPtrField<Extent>& extents, +bool BzipExtentWriter::Init(const RepeatedPtrField<Extent>& extents, uint32_t block_size) { // Init bzip2 stream int rc = BZ2_bzDecompressInit(&stream_, @@ -39,7 +38,7 @@ bool BzipExtentWriter::Init(FileDescriptorPtr fd, TEST_AND_RETURN_FALSE(rc == BZ_OK); - return next_->Init(fd, extents, block_size); + return next_->Init(extents, block_size); } bool BzipExtentWriter::Write(const void* bytes, size_t count) { diff --git a/payload_consumer/bzip_extent_writer.h b/payload_consumer/bzip_extent_writer.h index ec181a78..38c041a8 100644 --- a/payload_consumer/bzip_extent_writer.h +++ b/payload_consumer/bzip_extent_writer.h @@ -40,8 +40,7 @@ class BzipExtentWriter : public ExtentWriter { } ~BzipExtentWriter() override; - bool Init(FileDescriptorPtr fd, - const google::protobuf::RepeatedPtrField<Extent>& extents, + bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents, uint32_t block_size) override; bool Write(const void* bytes, size_t count) override; diff --git a/payload_consumer/bzip_extent_writer_unittest.cc b/payload_consumer/bzip_extent_writer_unittest.cc index 125e1e5e..c93545ab 100644 --- a/payload_consumer/bzip_extent_writer_unittest.cc +++ b/payload_consumer/bzip_extent_writer_unittest.cc @@ -29,7 +29,6 @@ #include "update_engine/common/utils.h" #include "update_engine/payload_generator/extent_ranges.h" -using google::protobuf::RepeatedPtrField; using std::min; using std::string; using std::vector; @@ -49,7 +48,7 @@ class BzipExtentWriterTest : public ::testing::Test { void TearDown() override { fd_->Close(); } FileDescriptorPtr fd_; - test_utils::ScopedTempFile temp_file_{"BzipExtentWriterTest-file.XXXXXX"}; + ScopedTempFile temp_file_{"BzipExtentWriterTest-file.XXXXXX"}; }; TEST_F(BzipExtentWriterTest, SimpleTest) { @@ -64,9 +63,8 @@ TEST_F(BzipExtentWriterTest, SimpleTest) { 0x22, 0x9c, 0x28, 0x48, 0x66, 0x61, 0xb8, 0xea, 0x00, }; - BzipExtentWriter bzip_writer(std::make_unique<DirectExtentWriter>()); - EXPECT_TRUE( - bzip_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize)); + BzipExtentWriter bzip_writer(std::make_unique<DirectExtentWriter>(fd_)); + EXPECT_TRUE(bzip_writer.Init({extents.begin(), extents.end()}, kBlockSize)); EXPECT_TRUE(bzip_writer.Write(test, sizeof(test))); brillo::Blob buf; @@ -97,9 +95,8 @@ TEST_F(BzipExtentWriterTest, ChunkedTest) { vector<Extent> extents = {ExtentForBytes(kBlockSize, 0, kDecompressedLength)}; - BzipExtentWriter bzip_writer(std::make_unique<DirectExtentWriter>()); - EXPECT_TRUE( - bzip_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize)); + BzipExtentWriter bzip_writer(std::make_unique<DirectExtentWriter>(fd_)); + EXPECT_TRUE(bzip_writer.Init({extents.begin(), extents.end()}, kBlockSize)); brillo::Blob original_compressed_data = compressed_data; for (brillo::Blob::size_type i = 0; i < compressed_data.size(); diff --git a/payload_consumer/cached_file_descriptor_unittest.cc b/payload_consumer/cached_file_descriptor_unittest.cc index d2965fc0..b64420a8 100644 --- a/payload_consumer/cached_file_descriptor_unittest.cc +++ b/payload_consumer/cached_file_descriptor_unittest.cc @@ -73,7 +73,7 @@ class CachedFileDescriptorTest : public ::testing::Test { protected: FileDescriptorPtr fd_{new EintrSafeFileDescriptor}; - test_utils::ScopedTempFile temp_file_{"CachedFileDescriptor-file.XXXXXX"}; + ScopedTempFile temp_file_{"CachedFileDescriptor-file.XXXXXX"}; int value_{1}; FileDescriptorPtr cfd_; }; diff --git a/payload_consumer/certificate_parser_stub.cc b/payload_consumer/certificate_parser_stub.cc index 95fd6e89..a365ab84 100644 --- a/payload_consumer/certificate_parser_stub.cc +++ b/payload_consumer/certificate_parser_stub.cc @@ -14,7 +14,7 @@ // limitations under the License. // -#include <payload_consumer/certificate_parser_stub.h> +#include "update_engine/payload_consumer/certificate_parser_stub.h" namespace chromeos_update_engine { bool CertificateParserStub::ReadPublicKeysFromCertificates( diff --git a/payload_consumer/certificate_parser_stub.h b/payload_consumer/certificate_parser_stub.h index f4f8825f..a51c2c67 100644 --- a/payload_consumer/certificate_parser_stub.h +++ b/payload_consumer/certificate_parser_stub.h @@ -23,7 +23,7 @@ #include <base/macros.h> -#include "payload_consumer/certificate_parser_interface.h" +#include "update_engine/payload_consumer/certificate_parser_interface.h" namespace chromeos_update_engine { class CertificateParserStub : public CertificateParserInterface { diff --git a/payload_consumer/cow_writer_file_descriptor.cc b/payload_consumer/cow_writer_file_descriptor.cc new file mode 100644 index 00000000..2de6664a --- /dev/null +++ b/payload_consumer/cow_writer_file_descriptor.cc @@ -0,0 +1,151 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_consumer/cow_writer_file_descriptor.h" + +#include <memory> +#include <utility> + +#include <base/logging.h> + +#include "update_engine/common/utils.h" +#include "update_engine/payload_consumer/file_descriptor.h" + +namespace chromeos_update_engine { +CowWriterFileDescriptor::CowWriterFileDescriptor( + std::unique_ptr<android::snapshot::ISnapshotWriter> cow_writer) + : cow_writer_(std::move(cow_writer)), + cow_reader_(cow_writer_->OpenReader()) { + CHECK_NE(cow_writer_, nullptr); + CHECK_NE(cow_reader_, nullptr); +} + +bool CowWriterFileDescriptor::Open(const char* path, int flags, mode_t mode) { + LOG(ERROR) << "CowWriterFileDescriptor doesn't support Open()"; + return false; +} +bool CowWriterFileDescriptor::Open(const char* path, int flags) { + LOG(ERROR) << "CowWriterFileDescriptor doesn't support Open()"; + return false; +} + +ssize_t CowWriterFileDescriptor::Read(void* buf, size_t count) { + if (dirty_) { + // OK, CowReader provides a snapshot view of what the cow contains. Which + // means any writes happened after opening a CowReader isn't visible to + // that CowReader. Therefore, we re-open CowReader whenever we attempt a + // read after write. This does incur an overhead everytime you read after + // write. + // The usage of |dirty_| flag to coordinate re-open is a very coarse grained + // checked. This implementation has suboptimal performance. For better + // performance, keep track of blocks which are overwritten, and only re-open + // if reading a dirty block. + // TODO(b/173432386) Implement finer grained dirty checks + const auto offset = cow_reader_->Seek(0, SEEK_CUR); + cow_reader_.reset(); + if (!cow_writer_->Finalize()) { + LOG(ERROR) << "Failed to Finalize() cow writer"; + return -1; + } + cow_reader_ = cow_writer_->OpenReader(); + if (cow_reader_ == nullptr) { + LOG(ERROR) << "Failed to re-open cow reader after writing to COW"; + return -1; + } + const auto pos = cow_reader_->Seek(offset, SEEK_SET); + if (pos != offset) { + LOG(ERROR) << "Failed to seek to previous position after re-opening cow " + "reader, expected " + << offset << " actual: " << pos; + return -1; + } + dirty_ = false; + } + return cow_reader_->Read(buf, count); +} + +ssize_t CowWriterFileDescriptor::Write(const void* buf, size_t count) { + auto offset = cow_reader_->Seek(0, SEEK_CUR); + CHECK_EQ(offset % cow_writer_->options().block_size, 0); + auto success = cow_writer_->AddRawBlocks( + offset / cow_writer_->options().block_size, buf, count); + if (success) { + if (cow_reader_->Seek(count, SEEK_CUR) < 0) { + return -1; + } + dirty_ = true; + return count; + } + return -1; +} + +off64_t CowWriterFileDescriptor::Seek(const off64_t offset, int whence) { + return cow_reader_->Seek(offset, whence); +} + +uint64_t CowWriterFileDescriptor::BlockDevSize() { + LOG(ERROR) << "CowWriterFileDescriptor doesn't support BlockDevSize()"; + return 0; +} + +bool CowWriterFileDescriptor::BlkIoctl(int request, + uint64_t start, + uint64_t length, + int* result) { + LOG(ERROR) << "CowWriterFileDescriptor doesn't support BlkIoctl()"; + return false; +} + +bool CowWriterFileDescriptor::Flush() { + // CowWriter already automatilly flushes, no need to do anything. + return true; +} + +bool CowWriterFileDescriptor::Close() { + if (cow_writer_) { + // b/186196758 + // When calling + // InitializeAppend(kEndOfInstall), the SnapshotWriter only reads up to the + // given label. But OpenReader() completely disregards the resume label and + // reads all ops. Therefore, update_engine sees the verity data. However, + // when calling SnapshotWriter::Finalize(), data after resume label are + // discarded, therefore verity data is gone. To prevent phantom reads, don't + // call Finalize() unless we actually write something. + if (dirty_) { + TEST_AND_RETURN_FALSE(cow_writer_->Finalize()); + } + cow_writer_ = nullptr; + } + if (cow_reader_) { + TEST_AND_RETURN_FALSE(cow_reader_->Close()); + cow_reader_ = nullptr; + } + return true; +} + +bool CowWriterFileDescriptor::IsSettingErrno() { + return false; +} + +bool CowWriterFileDescriptor::IsOpen() { + return cow_writer_ != nullptr && cow_reader_ != nullptr; +} + +CowWriterFileDescriptor::~CowWriterFileDescriptor() { + Close(); +} + +} // namespace chromeos_update_engine diff --git a/payload_consumer/cow_writer_file_descriptor.h b/payload_consumer/cow_writer_file_descriptor.h new file mode 100644 index 00000000..5d9ffc64 --- /dev/null +++ b/payload_consumer/cow_writer_file_descriptor.h @@ -0,0 +1,66 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <cstdint> +#include <memory> + +#include <libsnapshot/snapshot_writer.h> + +#include "update_engine/payload_consumer/file_descriptor.h" + +namespace chromeos_update_engine { + +// A Readable/Writable FileDescriptor class. This is a simple wrapper around +// CowWriter. Only intended to be used by FileSystemVerifierAction for writing +// FEC. Writes must be block aligned(4096) or write will fail. +class CowWriterFileDescriptor final : public FileDescriptor { + public: + explicit CowWriterFileDescriptor( + std::unique_ptr<android::snapshot::ISnapshotWriter> cow_writer); + ~CowWriterFileDescriptor(); + + bool Open(const char* path, int flags, mode_t mode) override; + bool Open(const char* path, int flags) override; + + ssize_t Read(void* buf, size_t count) override; + + // |count| must be block aligned, current offset of this fd must also be block + // aligned. + ssize_t Write(const void* buf, size_t count) override; + + off64_t Seek(off64_t offset, int whence) override; + + uint64_t BlockDevSize() override; + + bool BlkIoctl(int request, + uint64_t start, + uint64_t length, + int* result) override; + + bool Flush() override; + + bool Close() override; + + bool IsSettingErrno() override; + + bool IsOpen() override; + + private: + std::unique_ptr<android::snapshot::ISnapshotWriter> cow_writer_; + FileDescriptorPtr cow_reader_; + bool dirty_ = false; +}; +} // namespace chromeos_update_engine diff --git a/payload_consumer/cow_writer_file_descriptor_unittest.cc b/payload_consumer/cow_writer_file_descriptor_unittest.cc new file mode 100644 index 00000000..c596e3bb --- /dev/null +++ b/payload_consumer/cow_writer_file_descriptor_unittest.cc @@ -0,0 +1,120 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_consumer/cow_writer_file_descriptor.h" + +#include <cstring> +#include <memory> +#include <utility> +#include <vector> + +#include <android-base/unique_fd.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <libsnapshot/snapshot_writer.h> + +#include "update_engine/common/utils.h" + +namespace chromeos_update_engine { +constexpr size_t BLOCK_SIZE = 4096; +constexpr size_t PARTITION_SIZE = BLOCK_SIZE * 10; + +using android::base::unique_fd; +using android::snapshot::CompressedSnapshotWriter; +using android::snapshot::CowOptions; +using android::snapshot::ISnapshotWriter; + +class CowWriterFileDescriptorUnittest : public ::testing::Test { + public: + void SetUp() override { + ASSERT_EQ(ftruncate64(cow_device_file_.fd(), PARTITION_SIZE), 0) + << "Failed to truncate cow_device file to " << PARTITION_SIZE + << strerror(errno); + ASSERT_EQ(ftruncate64(cow_source_file_.fd(), PARTITION_SIZE), 0) + << "Failed to truncate cow_source file to " << PARTITION_SIZE + << strerror(errno); + } + + std::unique_ptr<CompressedSnapshotWriter> GetCowWriter() { + const CowOptions options{.block_size = BLOCK_SIZE, .compression = "gz"}; + auto snapshot_writer = std::make_unique<CompressedSnapshotWriter>(options); + int fd = open(cow_device_file_.path().c_str(), O_RDWR); + EXPECT_NE(fd, -1); + EXPECT_TRUE(snapshot_writer->SetCowDevice(unique_fd{fd})); + snapshot_writer->SetSourceDevice(cow_source_file_.path()); + return snapshot_writer; + } + CowWriterFileDescriptor GetCowFd() { + auto cow_writer = GetCowWriter(); + return CowWriterFileDescriptor{std::move(cow_writer)}; + } + + ScopedTempFile cow_source_file_{"cow_source.XXXXXX", true}; + ScopedTempFile cow_device_file_{"cow_device.XXXXXX", true}; +}; + +TEST_F(CowWriterFileDescriptorUnittest, ReadAfterWrite) { + std::vector<unsigned char> buffer; + buffer.resize(BLOCK_SIZE); + std::fill(buffer.begin(), buffer.end(), 234); + + std::vector<unsigned char> verity_data; + verity_data.resize(BLOCK_SIZE); + std::fill(verity_data.begin(), verity_data.end(), 0xAA); + + auto cow_writer = GetCowWriter(); + cow_writer->Initialize(); + + // Simulate Writing InstallOp data + ASSERT_TRUE(cow_writer->AddRawBlocks(0, buffer.data(), buffer.size())); + ASSERT_TRUE(cow_writer->AddZeroBlocks(1, 2)); + ASSERT_TRUE(cow_writer->AddCopy(3, 1)); + // Fake label to simulate "end of install" + ASSERT_TRUE(cow_writer->AddLabel(23)); + ASSERT_TRUE( + cow_writer->AddRawBlocks(4, verity_data.data(), verity_data.size())); + ASSERT_TRUE(cow_writer->Finalize()); + + cow_writer = GetCowWriter(); + ASSERT_NE(nullptr, cow_writer); + ASSERT_TRUE(cow_writer->InitializeAppend(23)); + auto cow_fd = + std::make_unique<CowWriterFileDescriptor>(std::move(cow_writer)); + + ASSERT_EQ((ssize_t)BLOCK_SIZE * 4, cow_fd->Seek(BLOCK_SIZE * 4, SEEK_SET)); + std::vector<unsigned char> read_back(4096); + ASSERT_EQ((ssize_t)read_back.size(), + cow_fd->Read(read_back.data(), read_back.size())); + ASSERT_EQ(verity_data, read_back); + + // Since we didn't write anything to this instance of cow_fd, destructor + // should not call Finalize(). As finalize will drop ops after resume label, + // causing subsequent reads to fail. + cow_writer = GetCowWriter(); + ASSERT_NE(nullptr, cow_writer); + ASSERT_TRUE(cow_writer->InitializeAppend(23)); + cow_fd = std::make_unique<CowWriterFileDescriptor>(std::move(cow_writer)); + + ASSERT_EQ((ssize_t)BLOCK_SIZE * 4, cow_fd->Seek(BLOCK_SIZE * 4, SEEK_SET)); + ASSERT_EQ((ssize_t)read_back.size(), + cow_fd->Read(read_back.data(), read_back.size())); + ASSERT_EQ(verity_data, read_back) + << "Could not read verity data afeter InitializeAppend() => Read() => " + "InitializeAppend() sequence. If no writes happened while CowWriterFd " + "is open, Finalize() should not be called."; +} + +} // namespace chromeos_update_engine diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc index 15973e93..a57169b5 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,24 +41,26 @@ #include <puffin/puffpatch.h> #include "update_engine/common/constants.h" +#include "update_engine/common/download_action.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/common/utils.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" +#include "update_engine/payload_consumer/partition_writer.h" #if USE_FEC #include "update_engine/payload_consumer/fec_file_descriptor.h" #endif // USE_FEC #include "update_engine/payload_consumer/file_descriptor_utils.h" #include "update_engine/payload_consumer/mount_history.h" -#if USE_MTD -#include "update_engine/payload_consumer/mtd_file_descriptor.h" -#endif // USE_MTD #include "update_engine/payload_consumer/payload_constants.h" #include "update_engine/payload_consumer/payload_verifier.h" #include "update_engine/payload_consumer/xz_extent_writer.h" @@ -77,103 +80,6 @@ const uint64_t DeltaPerformer::kCheckpointFrequencySeconds = 1; namespace { const int kUpdateStateOperationInvalid = -1; const int kMaxResumedUpdateFailures = 10; -#if USE_MTD -const int kUbiVolumeAttachTimeout = 5 * 60; -#endif - -const uint64_t kCacheSize = 1024 * 1024; // 1MB - -FileDescriptorPtr CreateFileDescriptor(const char* path) { - FileDescriptorPtr ret; -#if USE_MTD - if (strstr(path, "/dev/ubi") == path) { - if (!UbiFileDescriptor::IsUbi(path)) { - // The volume might not have been attached at boot time. - int volume_no; - if (utils::SplitPartitionName(path, nullptr, &volume_no)) { - utils::TryAttachingUbiVolume(volume_no, kUbiVolumeAttachTimeout); - } - } - if (UbiFileDescriptor::IsUbi(path)) { - LOG(INFO) << path << " is a UBI device."; - ret.reset(new UbiFileDescriptor); - } - } else if (MtdFileDescriptor::IsMtd(path)) { - LOG(INFO) << path << " is an MTD device."; - ret.reset(new MtdFileDescriptor); - } else { - LOG(INFO) << path << " is not an MTD nor a UBI device."; -#endif - ret.reset(new EintrSafeFileDescriptor); -#if USE_MTD - } -#endif - return ret; -} - -// Opens path for read/write. On success returns an open FileDescriptor -// and sets *err to 0. On failure, sets *err to errno and returns nullptr. -FileDescriptorPtr OpenFile(const char* path, - int mode, - bool cache_writes, - int* err) { - // Try to mark the block device read-only based on the mode. Ignore any - // failure since this won't work when passing regular files. - bool read_only = (mode & O_ACCMODE) == O_RDONLY; - utils::SetBlockDeviceReadOnly(path, read_only); - - FileDescriptorPtr fd = CreateFileDescriptor(path); - if (cache_writes && !read_only) { - fd = FileDescriptorPtr(new CachedFileDescriptor(fd, kCacheSize)); - LOG(INFO) << "Caching writes."; - } -#if USE_MTD - // On NAND devices, we can either read, or write, but not both. So here we - // use O_WRONLY. - if (UbiFileDescriptor::IsUbi(path) || MtdFileDescriptor::IsMtd(path)) { - mode = O_WRONLY; - } -#endif - if (!fd->Open(path, mode, 000)) { - *err = errno; - PLOG(ERROR) << "Unable to open file " << path; - return nullptr; - } - *err = 0; - return fd; -} - -// Discard the tail of the block device referenced by |fd|, from the offset -// |data_size| until the end of the block device. Returns whether the data was -// discarded. -bool DiscardPartitionTail(const FileDescriptorPtr& fd, uint64_t data_size) { - uint64_t part_size = fd->BlockDevSize(); - if (!part_size || part_size <= data_size) - return false; - - struct blkioctl_request { - int number; - const char* name; - }; - const vector<blkioctl_request> blkioctl_requests = { - {BLKDISCARD, "BLKDISCARD"}, - {BLKSECDISCARD, "BLKSECDISCARD"}, -#ifdef BLKZEROOUT - {BLKZEROOUT, "BLKZEROOUT"}, -#endif - }; - for (const auto& req : blkioctl_requests) { - int error = 0; - if (fd->BlkIoctl(req.number, data_size, part_size - data_size, &error) && - error == 0) { - return true; - } - LOG(WARNING) << "Error discarding the last " - << (part_size - data_size) / 1024 << " KiB using ioctl(" - << req.name << ")"; - } - return false; -} } // namespace @@ -292,12 +198,9 @@ bool DeltaPerformer::HandleOpResult(bool op_result, if (op_result) return true; - size_t partition_first_op_num = - current_partition_ ? acc_num_operations_[current_partition_ - 1] : 0; LOG(ERROR) << "Failed to perform " << op_type_name << " operation " << next_operation_num_ << ", which is the operation " - << next_operation_num_ - partition_first_op_num - << " in partition \"" + << GetPartitionOperationNum() << " in partition \"" << partitions_[current_partition_].partition_name() << "\""; if (*error == ErrorCode::kSuccess) *error = ErrorCode::kDownloadOperationExecutionError; @@ -319,33 +222,12 @@ int DeltaPerformer::Close() { } int DeltaPerformer::CloseCurrentPartition() { - int err = 0; - if (source_fd_ && !source_fd_->Close()) { - err = errno; - PLOG(ERROR) << "Error closing source partition"; - if (!err) - err = 1; - } - source_fd_.reset(); - if (source_ecc_fd_ && !source_ecc_fd_->Close()) { - err = errno; - PLOG(ERROR) << "Error closing ECC source partition"; - if (!err) - err = 1; - } - source_ecc_fd_.reset(); - source_ecc_open_failure_ = false; - source_path_.clear(); - - if (target_fd_ && !target_fd_->Close()) { - err = errno; - PLOG(ERROR) << "Error closing target partition"; - if (!err) - err = 1; + if (!partition_writer_) { + return 0; } - target_fd_.reset(); - target_path_.clear(); - return -err; + int err = partition_writer_->Close(); + partition_writer_ = nullptr; + return err; } bool DeltaPerformer::OpenCurrentPartition() { @@ -357,91 +239,29 @@ 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 with minor version >= 2. - if (payload_->type == InstallPayloadType::kDelta && - GetMinorVersion() != kInPlaceMinorPayloadVersion && - // With dynamic partitions we could create a new partition in a - // delta payload, and we shouldn't open source partition in that case. - install_part.source_size > 0) { - source_path_ = install_part.source_path; - int err; - source_fd_ = OpenFile(source_path_.c_str(), O_RDONLY, false, &err); - if (!source_fd_) { - LOG(ERROR) << "Unable to open source partition " - << partition.partition_name() << " on slot " - << BootControlInterface::SlotName(install_plan_->source_slot) - << ", file " << source_path_; - return false; - } - } - - target_path_ = install_part.target_path; - int err; - - int flags = O_RDWR; - if (!interactive_) - flags |= O_DSYNC; - - LOG(INFO) << "Opening " << target_path_ << " partition with" - << (interactive_ ? "out" : "") << " O_DSYNC"; - - target_fd_ = OpenFile(target_path_.c_str(), flags, true, &err); - if (!target_fd_) { - LOG(ERROR) << "Unable to open target partition " - << partition.partition_name() << " on slot " - << BootControlInterface::SlotName(install_plan_->target_slot) - << ", file " << target_path_; - return false; - } - - LOG(INFO) << "Applying " << partition.operations().size() - << " operations to partition \"" << partition.partition_name() - << "\""; - - // Discard the end of the partition, but ignore failures. - DiscardPartitionTail(target_fd_, install_part.target_size); - + auto dynamic_control = boot_control_->GetDynamicPartitionControl(); + partition_writer_ = CreatePartitionWriter( + partition, + install_part, + dynamic_control, + block_size_, + interactive_, + IsDynamicPartition(install_part.name, install_plan_->target_slot)); + // 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; + const size_t partition_operation_num = GetPartitionOperationNum(); + + TEST_AND_RETURN_FALSE(partition_writer_->Init( + install_plan_, source_may_exist, partition_operation_num)); + CheckpointUpdateProgress(true); return true; } -bool DeltaPerformer::OpenCurrentECCPartition() { - if (source_ecc_fd_) - return true; - - if (source_ecc_open_failure_) - return false; - - if (current_partition_ >= partitions_.size()) - return false; - - // No support for ECC in minor version 1 or full payloads. - if (payload_->type == InstallPayloadType::kFull || - GetMinorVersion() == kInPlaceMinorPayloadVersion) - return false; - -#if USE_FEC - const PartitionUpdate& partition = partitions_[current_partition_]; - size_t num_previous_partitions = - install_plan_->partitions.size() - partitions_.size(); - const InstallPlan::Partition& install_part = - install_plan_->partitions[num_previous_partitions + current_partition_]; - string path = install_part.source_path; - FileDescriptorPtr fd(new FecFileDescriptor()); - if (!fd->Open(path.c_str(), O_RDONLY, 0)) { - PLOG(ERROR) << "Unable to open ECC source partition " - << partition.partition_name() << " on slot " - << BootControlInterface::SlotName(install_plan_->source_slot) - << ", file " << path; - source_ecc_open_failure_ = true; - return false; - } - source_ecc_fd_ = fd; -#else - // No support for ECC compiled. - source_ecc_open_failure_ = true; -#endif // USE_FEC - - return !source_ecc_open_failure_; +size_t DeltaPerformer::GetPartitionOperationNum() { + return next_operation_num_ - + (current_partition_ ? acc_num_operations_[current_partition_ - 1] : 0); } namespace { @@ -465,15 +285,6 @@ void LogPartitionInfo(const vector<PartitionUpdate>& partitions) { } // namespace -uint32_t DeltaPerformer::GetMinorVersion() const { - if (manifest_.has_minor_version()) { - return manifest_.minor_version(); - } - return payload_->type == InstallPayloadType::kDelta - ? kMaxSupportedMinorPayloadVersion - : kFullPayloadMinorVersion; -} - bool DeltaPerformer::IsHeaderParsed() const { return metadata_size_ != 0; } @@ -505,6 +316,21 @@ MetadataParseResult DeltaPerformer::ParsePayloadMetadata( return MetadataParseResult::kError; } } + + // Check that the |metadata signature size_| and |metadata_size_| are not + // very big numbers. This is necessary since |update_engine| needs to write + // these values into the buffer before being able to use them, and if an + // attacker sets these values to a very big number, the buffer will overflow + // and |update_engine| will crash. A simple way of solving this is to check + // that the size of both values is smaller than the payload itself. + if (metadata_size_ + metadata_signature_size_ > payload_->size) { + LOG(ERROR) << "The size of the metadata_size(" << metadata_size_ << ")" + << " or metadata signature(" << metadata_signature_size_ << ")" + << " is greater than the size of the payload" + << "(" << payload_->size << ")"; + *error = ErrorCode::kDownloadInvalidMetadataSize; + return MetadataParseResult::kError; + } } // Now that we have validated the metadata size, we should wait for the full @@ -527,6 +353,7 @@ MetadataParseResult DeltaPerformer::ParsePayloadMetadata( << "Trusting metadata size in payload = " << metadata_size_; } + // NOLINTNEXTLINE(whitespace/braces) auto [payload_verifier, perform_verification] = CreatePayloadVerifier(); if (!payload_verifier) { LOG(ERROR) << "Failed to create payload verifier."; @@ -567,7 +394,7 @@ MetadataParseResult DeltaPerformer::ParsePayloadMetadata( #define OP_DURATION_HISTOGRAM(_op_name, _start_time) \ LOCAL_HISTOGRAM_CUSTOM_TIMES( \ "UpdateEngine.DownloadAction.InstallOperation::" _op_name ".Duration", \ - base::TimeTicks::Now() - _start_time, \ + (base::TimeTicks::Now() - _start_time), \ base::TimeDelta::FromMilliseconds(10), \ base::TimeDelta::FromMinutes(5), \ 20); @@ -608,6 +435,10 @@ 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) { + auto begin = reinterpret_cast<const char*>(buffer_.data()); + prefs_->SetString(kPrefsManifestBytes, {begin, buffer_.size()}); + } // Clear the download buffer. DiscardBuffer(false, metadata_size_); @@ -667,6 +498,9 @@ bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode* error) { // We know there are more operations to perform because we didn't reach the // |num_total_operations_| limit yet. if (next_operation_num_ >= acc_num_operations_[current_partition_]) { + if (partition_writer_) { + TEST_AND_RETURN_FALSE(partition_writer_->FinishedInstallOps()); + } CloseCurrentPartition(); // Skip until there are operations for current_partition_. while (next_operation_num_ >= acc_num_operations_[current_partition_]) { @@ -677,12 +511,9 @@ bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode* error) { return false; } } - const size_t partition_operation_num = - next_operation_num_ - - (current_partition_ ? acc_num_operations_[current_partition_ - 1] : 0); const InstallOperation& op = - partitions_[current_partition_].operations(partition_operation_num); + partitions_[current_partition_].operations(GetPartitionOperationNum()); CopyDataToBuffer(&c_bytes, &count, op.data_length()); @@ -729,14 +560,6 @@ bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode* error) { op_result = PerformZeroOrDiscardOperation(op); OP_DURATION_HISTOGRAM("ZERO_OR_DISCARD", op_start_time); break; - case InstallOperation::MOVE: - op_result = PerformMoveOperation(op); - OP_DURATION_HISTOGRAM("MOVE", op_start_time); - break; - case InstallOperation::BSDIFF: - op_result = PerformBsdiffOperation(op); - OP_DURATION_HISTOGRAM("BSDIFF", op_start_time); - break; case InstallOperation::SOURCE_COPY: op_result = PerformSourceCopyOperation(op, error); OP_DURATION_HISTOGRAM("SOURCE_COPY", op_start_time); @@ -756,19 +579,19 @@ bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode* error) { if (!HandleOpResult(op_result, InstallOperationTypeName(op.type()), error)) return false; - if (!target_fd_->Flush()) { - return false; - } - next_operation_num_++; UpdateOverallProgress(false, "Completed "); CheckpointUpdateProgress(false); } - // In major version 2, we don't add dummy operation to the payload. + if (partition_writer_) { + TEST_AND_RETURN_FALSE(partition_writer_->FinishedInstallOps()); + } + CloseCurrentPartition(); + + // 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 (major_payload_version_ == kBrilloMajorPayloadVersion && - manifest_.has_signatures_offset() && manifest_.has_signatures_size() && + if (manifest_.has_signatures_offset() && manifest_.has_signatures_size() && signatures_message_data_.empty()) { if (manifest_.signatures_offset() != buffer_offset_) { LOG(ERROR) << "Payload signatures offset points to blob offset " @@ -803,49 +626,65 @@ bool DeltaPerformer::IsManifestValid() { } bool DeltaPerformer::ParseManifestPartitions(ErrorCode* error) { - if (major_payload_version_ == kBrilloMajorPayloadVersion) { - partitions_.clear(); - for (const PartitionUpdate& partition : manifest_.partitions()) { - partitions_.push_back(partition); - } - } else if (major_payload_version_ == kChromeOSMajorPayloadVersion) { - LOG(INFO) << "Converting update information from old format."; - PartitionUpdate root_part; - root_part.set_partition_name(kPartitionNameRoot); -#ifdef __ANDROID__ - LOG(WARNING) << "Legacy payload major version provided to an Android " - "build. Assuming no post-install. Please use major version " - "2 or newer."; - root_part.set_run_postinstall(false); -#else - root_part.set_run_postinstall(true); -#endif // __ANDROID__ - if (manifest_.has_old_rootfs_info()) { - *root_part.mutable_old_partition_info() = manifest_.old_rootfs_info(); - manifest_.clear_old_rootfs_info(); + partitions_.clear(); + 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; } - if (manifest_.has_new_rootfs_info()) { - *root_part.mutable_new_partition_info() = manifest_.new_rootfs_info(); - manifest_.clear_new_rootfs_info(); + } + + // 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()); } - *root_part.mutable_operations() = manifest_.install_operations(); - manifest_.clear_install_operations(); - partitions_.push_back(std::move(root_part)); - - PartitionUpdate kern_part; - kern_part.set_partition_name(kPartitionNameKernel); - kern_part.set_run_postinstall(false); - if (manifest_.has_old_kernel_info()) { - *kern_part.mutable_old_partition_info() = manifest_.old_kernel_info(); - manifest_.clear_old_kernel_info(); + + 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, + boot_control_->GetCurrentSlot(), + &dynamic_partitions)) { + LOG(ERROR) << "Failed to load dynamic partitions from slot " + << install_plan_->source_slot; + return false; } - if (manifest_.has_new_kernel_info()) { - *kern_part.mutable_new_partition_info() = manifest_.new_kernel_info(); - manifest_.clear_new_kernel_info(); + 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); + } } - *kern_part.mutable_operations() = manifest_.kernel_install_operations(); - manifest_.clear_kernel_install_operations(); - partitions_.push_back(std::move(kern_part)); } // Fill in the InstallPlan::partitions based on the partitions from the @@ -921,22 +760,9 @@ bool DeltaPerformer::ParseManifestPartitions(ErrorCode* error) { install_plan_->partitions.push_back(install_part); } - 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; - } - } - - if (major_payload_version_ == kBrilloMajorPayloadVersion) { - manifest_.clear_partitions(); - } - + // 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; @@ -981,6 +807,7 @@ bool DeltaPerformer::PreparePartitionsForUpdate( } else { LOG(INFO) << "Preparing partitions for new update. last hash = " << last_hash << ", new hash = " << update_check_response_hash; + ResetUpdateProgress(prefs, false); } if (!boot_control->GetDynamicPartitionControl()->PreparePartitionsForUpdate( @@ -1025,30 +852,10 @@ bool DeltaPerformer::PerformReplaceOperation( // Since we delete data off the beginning of the buffer as we use it, // the data we need should be exactly at the beginning of the buffer. - TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset()); TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length()); - // Extract the signature message if it's in this operation. - if (ExtractSignatureMessageFromOperation(operation)) { - // If this is dummy replace operation, we ignore it after extracting the - // signature. - DiscardBuffer(true, 0); - return true; - } - - // Setup the ExtentWriter stack based on the operation type. - std::unique_ptr<ExtentWriter> writer = std::make_unique<DirectExtentWriter>(); - - if (operation.type() == InstallOperation::REPLACE_BZ) { - writer.reset(new BzipExtentWriter(std::move(writer))); - } else if (operation.type() == InstallOperation::REPLACE_XZ) { - writer.reset(new XzExtentWriter(std::move(writer))); - } - - TEST_AND_RETURN_FALSE( - writer->Init(target_fd_, operation.dst_extents(), block_size_)); - TEST_AND_RETURN_FALSE(writer->Write(buffer_.data(), operation.data_length())); - + TEST_AND_RETURN_FALSE(partition_writer_->PerformReplaceOperation( + operation, buffer_.data(), buffer_.size())); // Update buffer DiscardBuffer(true, buffer_.size()); return true; @@ -1063,92 +870,13 @@ bool DeltaPerformer::PerformZeroOrDiscardOperation( TEST_AND_RETURN_FALSE(!operation.has_data_offset()); TEST_AND_RETURN_FALSE(!operation.has_data_length()); -#ifdef BLKZEROOUT - bool attempt_ioctl = true; - int request = - (operation.type() == InstallOperation::ZERO ? BLKZEROOUT : BLKDISCARD); -#else // !defined(BLKZEROOUT) - bool attempt_ioctl = false; - int request = 0; -#endif // !defined(BLKZEROOUT) - - brillo::Blob zeros; - for (const Extent& extent : operation.dst_extents()) { - const uint64_t start = extent.start_block() * block_size_; - const uint64_t length = extent.num_blocks() * block_size_; - if (attempt_ioctl) { - int result = 0; - if (target_fd_->BlkIoctl(request, start, length, &result) && result == 0) - continue; - attempt_ioctl = false; - } - // In case of failure, we fall back to writing 0 to the selected region. - zeros.resize(16 * block_size_); - for (uint64_t offset = 0; offset < length; offset += zeros.size()) { - uint64_t chunk_length = - min(length - offset, static_cast<uint64_t>(zeros.size())); - TEST_AND_RETURN_FALSE(utils::PWriteAll( - target_fd_, zeros.data(), chunk_length, start + offset)); - } - } - return true; + return partition_writer_->PerformZeroOrDiscardOperation(operation); } -bool DeltaPerformer::PerformMoveOperation(const InstallOperation& operation) { - // Calculate buffer size. Note, this function doesn't do a sliding - // window to copy in case the source and destination blocks overlap. - // If we wanted to do a sliding window, we could program the server - // to generate deltas that effectively did a sliding window. - - uint64_t blocks_to_read = 0; - for (int i = 0; i < operation.src_extents_size(); i++) - blocks_to_read += operation.src_extents(i).num_blocks(); - - uint64_t blocks_to_write = 0; - for (int i = 0; i < operation.dst_extents_size(); i++) - blocks_to_write += operation.dst_extents(i).num_blocks(); - - DCHECK_EQ(blocks_to_write, blocks_to_read); - brillo::Blob buf(blocks_to_write * block_size_); - - // Read in bytes. - ssize_t bytes_read = 0; - for (int i = 0; i < operation.src_extents_size(); i++) { - ssize_t bytes_read_this_iteration = 0; - const Extent& extent = operation.src_extents(i); - const size_t bytes = extent.num_blocks() * block_size_; - TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole); - TEST_AND_RETURN_FALSE(utils::PReadAll(target_fd_, - &buf[bytes_read], - bytes, - extent.start_block() * block_size_, - &bytes_read_this_iteration)); - TEST_AND_RETURN_FALSE(bytes_read_this_iteration == - static_cast<ssize_t>(bytes)); - bytes_read += bytes_read_this_iteration; - } - - // Write bytes out. - ssize_t bytes_written = 0; - for (int i = 0; i < operation.dst_extents_size(); i++) { - const Extent& extent = operation.dst_extents(i); - const size_t bytes = extent.num_blocks() * block_size_; - TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole); - TEST_AND_RETURN_FALSE(utils::PWriteAll(target_fd_, - &buf[bytes_written], - bytes, - extent.start_block() * block_size_)); - bytes_written += bytes; - } - DCHECK_EQ(bytes_written, bytes_read); - DCHECK_EQ(bytes_written, static_cast<ssize_t>(buf.size())); - return true; -} - -bool DeltaPerformer::ValidateSourceHash(const brillo::Blob& calculated_hash, - const InstallOperation& operation, - const FileDescriptorPtr source_fd, - ErrorCode* error) { +bool PartitionWriter::ValidateSourceHash(const brillo::Blob& calculated_hash, + const InstallOperation& operation, + const FileDescriptorPtr source_fd, + ErrorCode* error) { brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(), operation.src_sha256_hash().end()); if (calculated_hash != expected_source_hash) { @@ -1189,169 +917,7 @@ bool DeltaPerformer::PerformSourceCopyOperation( TEST_AND_RETURN_FALSE(operation.src_length() % block_size_ == 0); 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()); - - // We fall back to use the error corrected device if the hash of the raw - // device doesn't match or there was an error reading the source partition. - // Note that this code will also fall back if writing the target partition - // fails. - if (should_optimize) { - // Hash operation.src_extents(), then copy optimized.src_extents to - // optimized.dst_extents. - read_ok = - fd_utils::ReadAndHashExtents( - source_fd_, operation.src_extents(), block_size_, &source_hash) && - fd_utils::CopyAndHashExtents(source_fd_, - optimized.src_extents(), - target_fd_, - optimized.dst_extents(), - block_size_, - nullptr /* skip hashing */); - } else { - read_ok = fd_utils::CopyAndHashExtents(source_fd_, - operation.src_extents(), - target_fd_, - operation.dst_extents(), - block_size_, - &source_hash); - } - if (read_ok && expected_source_hash == source_hash) - return true; - - if (!OpenCurrentECCPartition()) { - // The following function call will return false since the source hash - // mismatches, but we still want to call it so it prints the appropriate - // log message. - return ValidateSourceHash(source_hash, operation, source_fd_, error); - } - - LOG(WARNING) << "Source hash from RAW device mismatched: found " - << base::HexEncode(source_hash.data(), source_hash.size()) - << ", expected " - << base::HexEncode(expected_source_hash.data(), - expected_source_hash.size()); - - if (should_optimize) { - TEST_AND_RETURN_FALSE(fd_utils::ReadAndHashExtents( - source_ecc_fd_, operation.src_extents(), block_size_, &source_hash)); - TEST_AND_RETURN_FALSE( - fd_utils::CopyAndHashExtents(source_ecc_fd_, - optimized.src_extents(), - target_fd_, - optimized.dst_extents(), - block_size_, - nullptr /* skip hashing */)); - } else { - TEST_AND_RETURN_FALSE( - fd_utils::CopyAndHashExtents(source_ecc_fd_, - operation.src_extents(), - target_fd_, - operation.dst_extents(), - block_size_, - &source_hash)); - } - TEST_AND_RETURN_FALSE( - ValidateSourceHash(source_hash, operation, source_ecc_fd_, error)); - // At this point reading from the the error corrected device worked, but - // reading from the raw device failed, so this is considered a recovered - // failure. - source_ecc_recovered_failures_++; - } else { - // When the operation doesn't include a source hash, we attempt the error - // corrected device first since we can't verify the block in the raw device - // at this point, but we fall back to the raw device since the error - // corrected device can be shorter or not available. - - if (OpenCurrentECCPartition() && - fd_utils::CopyAndHashExtents(source_ecc_fd_, - optimized.src_extents(), - target_fd_, - optimized.dst_extents(), - block_size_, - nullptr)) { - return true; - } - TEST_AND_RETURN_FALSE(fd_utils::CopyAndHashExtents(source_fd_, - optimized.src_extents(), - target_fd_, - optimized.dst_extents(), - block_size_, - nullptr)); - } - return true; -} - -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 - // at this point, but we first need to make sure all extents are readable - // since the error corrected device can be shorter or not available. - if (OpenCurrentECCPartition() && - fd_utils::ReadAndHashExtents( - source_ecc_fd_, operation.src_extents(), block_size_, nullptr)) { - return source_ecc_fd_; - } - return source_fd_; - } - - brillo::Blob source_hash; - brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(), - operation.src_sha256_hash().end()); - if (fd_utils::ReadAndHashExtents( - source_fd_, operation.src_extents(), block_size_, &source_hash) && - source_hash == expected_source_hash) { - return source_fd_; - } - // We fall back to use the error corrected device if the hash of the raw - // device doesn't match or there was an error reading the source partition. - if (!OpenCurrentECCPartition()) { - // The following function call will return false since the source hash - // mismatches, but we still want to call it so it prints the appropriate - // log message. - ValidateSourceHash(source_hash, operation, source_fd_, error); - return nullptr; - } - LOG(WARNING) << "Source hash from RAW device mismatched: found " - << base::HexEncode(source_hash.data(), source_hash.size()) - << ", expected " - << base::HexEncode(expected_source_hash.data(), - expected_source_hash.size()); - - if (fd_utils::ReadAndHashExtents( - source_ecc_fd_, operation.src_extents(), block_size_, &source_hash) && - ValidateSourceHash(source_hash, operation, source_ecc_fd_, error)) { - // At this point reading from the the error corrected device worked, but - // reading from the raw device failed, so this is considered a recovered - // failure. - source_ecc_recovered_failures_++; - return source_ecc_fd_; - } - return nullptr; + return partition_writer_->PerformSourceCopyOperation(operation, error); } bool DeltaPerformer::ExtentsToBsdiffPositionsString( @@ -1376,110 +942,6 @@ bool DeltaPerformer::ExtentsToBsdiffPositionsString( return true; } -bool DeltaPerformer::PerformBsdiffOperation(const InstallOperation& operation) { - // Since we delete data off the beginning of the buffer as we use it, - // the data we need should be exactly at the beginning of the buffer. - TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset()); - TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length()); - - string input_positions; - TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.src_extents(), - block_size_, - operation.src_length(), - &input_positions)); - string output_positions; - TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.dst_extents(), - block_size_, - operation.dst_length(), - &output_positions)); - - TEST_AND_RETURN_FALSE(bsdiff::bspatch(target_path_.c_str(), - target_path_.c_str(), - buffer_.data(), - buffer_.size(), - input_positions.c_str(), - output_positions.c_str()) == 0); - DiscardBuffer(true, buffer_.size()); - - if (operation.dst_length() % block_size_) { - // Zero out rest of final block. - // TODO(adlr): build this into bspatch; it's more efficient that way. - const Extent& last_extent = - operation.dst_extents(operation.dst_extents_size() - 1); - const uint64_t end_byte = - (last_extent.start_block() + last_extent.num_blocks()) * block_size_; - const uint64_t begin_byte = - end_byte - (block_size_ - operation.dst_length() % block_size_); - brillo::Blob zeros(end_byte - begin_byte); - TEST_AND_RETURN_FALSE(utils::PWriteAll( - target_fd_, zeros.data(), end_byte - begin_byte, begin_byte)); - } - return true; -} - -namespace { - -class BsdiffExtentFile : public bsdiff::FileInterface { - public: - BsdiffExtentFile(std::unique_ptr<ExtentReader> reader, size_t size) - : BsdiffExtentFile(std::move(reader), nullptr, size) {} - BsdiffExtentFile(std::unique_ptr<ExtentWriter> writer, size_t size) - : BsdiffExtentFile(nullptr, std::move(writer), size) {} - - ~BsdiffExtentFile() override = default; - - bool Read(void* buf, size_t count, size_t* bytes_read) override { - TEST_AND_RETURN_FALSE(reader_->Read(buf, count)); - *bytes_read = count; - offset_ += count; - return true; - } - - bool Write(const void* buf, size_t count, size_t* bytes_written) override { - TEST_AND_RETURN_FALSE(writer_->Write(buf, count)); - *bytes_written = count; - offset_ += count; - return true; - } - - bool Seek(off_t pos) override { - if (reader_ != nullptr) { - TEST_AND_RETURN_FALSE(reader_->Seek(pos)); - offset_ = pos; - } else { - // For writes technically there should be no change of position, or it - // should be equivalent of current offset. - TEST_AND_RETURN_FALSE(offset_ == static_cast<uint64_t>(pos)); - } - return true; - } - - bool Close() override { return true; } - - bool GetSize(uint64_t* size) override { - *size = size_; - return true; - } - - private: - BsdiffExtentFile(std::unique_ptr<ExtentReader> reader, - std::unique_ptr<ExtentWriter> writer, - size_t size) - : reader_(std::move(reader)), - writer_(std::move(writer)), - size_(size), - offset_(0) {} - - std::unique_ptr<ExtentReader> reader_; - std::unique_ptr<ExtentWriter> writer_; - uint64_t size_; - uint64_t offset_; - - DISALLOW_COPY_AND_ASSIGN(BsdiffExtentFile); -}; - -} // namespace - bool DeltaPerformer::PerformSourceBsdiffOperation( const InstallOperation& operation, ErrorCode* error) { // Since we delete data off the beginning of the buffer as we use it, @@ -1491,153 +953,24 @@ bool DeltaPerformer::PerformSourceBsdiffOperation( if (operation.has_dst_length()) TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0); - FileDescriptorPtr source_fd = ChooseSourceFD(operation, error); - TEST_AND_RETURN_FALSE(source_fd != nullptr); - - auto reader = std::make_unique<DirectExtentReader>(); - TEST_AND_RETURN_FALSE( - reader->Init(source_fd, operation.src_extents(), block_size_)); - auto src_file = std::make_unique<BsdiffExtentFile>( - std::move(reader), - utils::BlocksInExtents(operation.src_extents()) * block_size_); - - auto writer = std::make_unique<DirectExtentWriter>(); - TEST_AND_RETURN_FALSE( - writer->Init(target_fd_, operation.dst_extents(), block_size_)); - auto dst_file = std::make_unique<BsdiffExtentFile>( - std::move(writer), - utils::BlocksInExtents(operation.dst_extents()) * block_size_); - - TEST_AND_RETURN_FALSE(bsdiff::bspatch(std::move(src_file), - std::move(dst_file), - buffer_.data(), - buffer_.size()) == 0); + TEST_AND_RETURN_FALSE(partition_writer_->PerformSourceBsdiffOperation( + operation, error, buffer_.data(), buffer_.size())); DiscardBuffer(true, buffer_.size()); return true; } -namespace { - -// A class to be passed to |puffpatch| for reading from |source_fd_| and writing -// into |target_fd_|. -class PuffinExtentStream : public puffin::StreamInterface { - public: - // Constructor for creating a stream for reading from an |ExtentReader|. - PuffinExtentStream(std::unique_ptr<ExtentReader> reader, uint64_t size) - : PuffinExtentStream(std::move(reader), nullptr, size) {} - - // Constructor for creating a stream for writing to an |ExtentWriter|. - PuffinExtentStream(std::unique_ptr<ExtentWriter> writer, uint64_t size) - : PuffinExtentStream(nullptr, std::move(writer), size) {} - - ~PuffinExtentStream() override = default; - - bool GetSize(uint64_t* size) const override { - *size = size_; - return true; - } - - bool GetOffset(uint64_t* offset) const override { - *offset = offset_; - return true; - } - - bool Seek(uint64_t offset) override { - if (is_read_) { - TEST_AND_RETURN_FALSE(reader_->Seek(offset)); - offset_ = offset; - } else { - // For writes technically there should be no change of position, or it - // should equivalent of current offset. - TEST_AND_RETURN_FALSE(offset_ == offset); - } - return true; - } - - bool Read(void* buffer, size_t count) override { - TEST_AND_RETURN_FALSE(is_read_); - TEST_AND_RETURN_FALSE(reader_->Read(buffer, count)); - offset_ += count; - return true; - } - - bool Write(const void* buffer, size_t count) override { - TEST_AND_RETURN_FALSE(!is_read_); - TEST_AND_RETURN_FALSE(writer_->Write(buffer, count)); - offset_ += count; - return true; - } - - bool Close() override { return true; } - - private: - PuffinExtentStream(std::unique_ptr<ExtentReader> reader, - std::unique_ptr<ExtentWriter> writer, - uint64_t size) - : reader_(std::move(reader)), - writer_(std::move(writer)), - size_(size), - offset_(0), - is_read_(reader_ ? true : false) {} - - std::unique_ptr<ExtentReader> reader_; - std::unique_ptr<ExtentWriter> writer_; - uint64_t size_; - uint64_t offset_; - bool is_read_; - - DISALLOW_COPY_AND_ASSIGN(PuffinExtentStream); -}; - -} // namespace - bool DeltaPerformer::PerformPuffDiffOperation(const InstallOperation& operation, ErrorCode* error) { // Since we delete data off the beginning of the buffer as we use it, // the data we need should be exactly at the beginning of the buffer. TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset()); TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length()); - - FileDescriptorPtr source_fd = ChooseSourceFD(operation, error); - TEST_AND_RETURN_FALSE(source_fd != nullptr); - - auto reader = std::make_unique<DirectExtentReader>(); - TEST_AND_RETURN_FALSE( - reader->Init(source_fd, operation.src_extents(), block_size_)); - puffin::UniqueStreamPtr src_stream(new PuffinExtentStream( - std::move(reader), - utils::BlocksInExtents(operation.src_extents()) * block_size_)); - - auto writer = std::make_unique<DirectExtentWriter>(); - TEST_AND_RETURN_FALSE( - writer->Init(target_fd_, operation.dst_extents(), block_size_)); - puffin::UniqueStreamPtr dst_stream(new PuffinExtentStream( - std::move(writer), - utils::BlocksInExtents(operation.dst_extents()) * block_size_)); - - const size_t kMaxCacheSize = 5 * 1024 * 1024; // Total 5MB cache. - TEST_AND_RETURN_FALSE(puffin::PuffPatch(std::move(src_stream), - std::move(dst_stream), - buffer_.data(), - buffer_.size(), - kMaxCacheSize)); + TEST_AND_RETURN_FALSE(partition_writer_->PerformPuffDiffOperation( + operation, error, buffer_.data(), buffer_.size())); DiscardBuffer(true, buffer_.size()); return true; } -bool DeltaPerformer::ExtractSignatureMessageFromOperation( - const InstallOperation& operation) { - if (operation.type() != InstallOperation::REPLACE || - !manifest_.has_signatures_offset() || - manifest_.signatures_offset() != operation.data_offset()) { - return false; - } - TEST_AND_RETURN_FALSE(manifest_.has_signatures_size() && - manifest_.signatures_size() == operation.data_length()); - TEST_AND_RETURN_FALSE(ExtractSignatureMessage()); - return true; -} - bool DeltaPerformer::ExtractSignatureMessage() { TEST_AND_RETURN_FALSE(signatures_message_data_.empty()); TEST_AND_RETURN_FALSE(buffer_offset_ == manifest_.signatures_offset()); @@ -1645,20 +978,6 @@ bool DeltaPerformer::ExtractSignatureMessage() { signatures_message_data_.assign( buffer_.begin(), buffer_.begin() + manifest_.signatures_size()); - // Save the signature blob because if the update is interrupted after the - // download phase we don't go through this path anymore. Some alternatives to - // consider: - // - // 1. On resume, re-download the signature blob from the server and re-verify - // it. - // - // 2. Verify the signature as soon as it's received and don't checkpoint the - // blob and the signed sha-256 context. - LOG_IF(WARNING, - !prefs_->SetString(kPrefsUpdateStateSignatureBlob, - signatures_message_data_)) - << "Unable to store the signature blob."; - LOG(INFO) << "Extracted signature data of size " << manifest_.signatures_size() << " at " << manifest_.signatures_offset(); @@ -1673,8 +992,8 @@ bool DeltaPerformer::GetPublicKey(string* out_public_key) { return utils::ReadFile(public_key_path_, out_public_key); } - // If this is an official build then we are not allowed to use public key from - // Omaha response. + // If this is an official build then we are not allowed to use public key + // from Omaha response. if (!hardware_->IsOfficialBuild() && !install_plan_->public_key_rsa.empty()) { LOG(INFO) << "Verifying using public key from Omaha response."; return brillo::data_encoding::Base64Decode(install_plan_->public_key_rsa, @@ -1707,19 +1026,21 @@ DeltaPerformer::CreatePayloadVerifier() { } 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 = - (manifest_.has_old_kernel_info() || manifest_.has_old_rootfs_info()); - for (const PartitionUpdate& partition : manifest_.partitions()) { - has_old_fields = has_old_fields || partition.has_old_partition_info(); - } + bool has_old_fields = std::any_of(manifest_.partitions().begin(), + manifest_.partitions().end(), + [](const PartitionUpdate& partition) { + return partition.has_old_partition_info(); + }); // 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 '" @@ -1734,8 +1055,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 " @@ -1756,43 +1077,114 @@ ErrorCode DeltaPerformer::ValidateManifest() { } } - if (major_payload_version_ != kChromeOSMajorPayloadVersion) { - if (manifest_.has_old_rootfs_info() || manifest_.has_new_rootfs_info() || - manifest_.has_old_kernel_info() || manifest_.has_new_kernel_info() || - manifest_.install_operations_size() != 0 || - manifest_.kernel_install_operations_size() != 0) { - LOG(ERROR) << "Manifest contains deprecated field only supported in " - << "major payload version 1, but the payload major version is " - << major_payload_version_; - return ErrorCode::kPayloadMismatchedType; + 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 { + const auto& partition_name = partition.partition_name(); + if (!partition.has_version()) { + if (hardware_->GetVersionForLogging(partition_name).empty()) { + LOG(INFO) << partition_name << " does't have version, skipping " + << "downgrade check."; + return ErrorCode::kSuccess; + } + + if (allow_empty_version) { + return ErrorCode::kSuccess; + } + LOG(ERROR) + << "PartitionUpdate " << partition_name + << " doesn't have a version field. Not allowed in partial updates."; + return ErrorCode::kDownloadManifestParseError; + } + + auto error_code = + hardware_->IsPartitionUpdateValid(partition_name, partition.version()); + switch (error_code) { + case ErrorCode::kSuccess: + break; + case ErrorCode::kPayloadTimestampError: + *downgrade_detected = true; + LOG(WARNING) << "PartitionUpdate " << partition_name + << " has an older version than partition on device."; + break; + default: + LOG(ERROR) << "IsPartitionUpdateValid(" << 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() << ") is newer than the maximum timestamp in the manifest (" << manifest_.max_timestamp() << ")"; - if (!hardware_->AllowDowngrade()) { - return ErrorCode::kPayloadTimestampError; + return ErrorCode::kPayloadTimestampError; + } + // 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; } - LOG(INFO) << "The current OS build allows downgrade, continuing to apply" - " the payload with an older timestamp."; } - - if (major_payload_version_ == kChromeOSMajorPayloadVersion) { - if (manifest_.has_dynamic_partition_metadata()) { - LOG(ERROR) - << "Should not contain dynamic_partition_metadata for major version " - << kChromeOSMajorPayloadVersion - << ". Please use major version 2 or above."; - return ErrorCode::kPayloadMismatchedType; - } + if (downgrade_detected) { + return ErrorCode::kPayloadTimestampError; } - - // TODO(garnold) we should be adding more and more manifest checks, such as - // partition boundaries etc (see chromium-os:37661). - return ErrorCode::kSuccess; } @@ -1800,10 +1192,11 @@ ErrorCode DeltaPerformer::ValidateOperationHash( const InstallOperation& operation) { if (!operation.data_sha256_hash().size()) { if (!operation.data_length()) { - // Operations that do not have any data blob won't have any operation hash - // either. So, these operations are always considered validated since the - // metadata that contains all the non-data-blob portions of the operation - // has already been validated. This is true for both HTTP and HTTPS cases. + // Operations that do not have any data blob won't have any operation + // hash either. So, these operations are always considered validated + // since the metadata that contains all the non-data-blob portions of + // the operation has already been validated. This is true for both HTTP + // and HTTPS cases. return ErrorCode::kSuccess; } @@ -1812,7 +1205,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. @@ -1887,12 +1280,7 @@ ErrorCode DeltaPerformer::VerifyPayload( ErrorCode::kPayloadHashMismatchError, payload_hash_calculator_.raw_hash() == update_check_response_hash); - 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, - hash_data.size() == kSHA256Size); - + // NOLINTNEXTLINE(whitespace/braces) auto [payload_verifier, perform_verification] = CreatePayloadVerifier(); if (!perform_verification) { LOG(WARNING) << "Not verifying signed delta payload -- missing public key."; @@ -1902,6 +1290,13 @@ ErrorCode DeltaPerformer::VerifyPayload( LOG(ERROR) << "Failed to create the payload verifier."; return ErrorCode::kDownloadPayloadPubKeyVerificationError; } + + 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, + hash_data.size() == kSHA256Size); + if (!payload_verifier->VerifySignature(signatures_message_data_, hash_data)) { // The autoupdate_CatchBadSignatures test checks for this string // in log-files. Keep in sync. @@ -1941,13 +1336,13 @@ bool DeltaPerformer::CanResumeUpdate(PrefsInterface* prefs, return false; int64_t resumed_update_failures; - // Note that storing this value is optional, but if it is there it should not - // be more than the limit. + // Note that storing this value is optional, but if it is there it should + // not be more than the limit. if (prefs->GetInt64(kPrefsResumedUpdateFailures, &resumed_update_failures) && 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)) @@ -1998,18 +1393,38 @@ bool DeltaPerformer::ResetUpdateProgress( return true; } -bool DeltaPerformer::CheckpointUpdateProgress(bool force) { +bool DeltaPerformer::ShouldCheckpoint() { base::TimeTicks curr_time = base::TimeTicks::Now(); - if (force || curr_time > update_checkpoint_time_) { + if (curr_time > update_checkpoint_time_) { update_checkpoint_time_ = curr_time + update_checkpoint_wait_; - } else { - return false; + return true; } + return false; +} +bool DeltaPerformer::CheckpointUpdateProgress(bool force) { + if (!force && !ShouldCheckpoint()) { + return false; + } Terminator::set_exit_blocked(true); - if (last_updated_buffer_offset_ != buffer_offset_) { + if (last_updated_operation_num_ != next_operation_num_ || force) { // Resets the progress in case we die in the middle of the state update. ResetUpdateProgress(prefs_, true); + if (!signatures_message_data_.empty()) { + // Save the signature blob because if the update is interrupted after the + // download phase we don't go through this path anymore. Some alternatives + // to consider: + // + // 1. On resume, re-download the signature blob from the server and + // re-verify it. + // + // 2. Verify the signature as soon as it's received and don't checkpoint + // the blob and the signed sha-256 context. + LOG_IF(WARNING, + !prefs_->SetString(kPrefsUpdateStateSignatureBlob, + signatures_message_data_)) + << "Unable to store the signature blob."; + } TEST_AND_RETURN_FALSE(prefs_->SetString( kPrefsUpdateStateSHA256Context, payload_hash_calculator_.GetContext())); TEST_AND_RETURN_FALSE( @@ -2017,12 +1432,13 @@ bool DeltaPerformer::CheckpointUpdateProgress(bool force) { signed_hash_calculator_.GetContext())); TEST_AND_RETURN_FALSE( prefs_->SetInt64(kPrefsUpdateStateNextDataOffset, buffer_offset_)); - last_updated_buffer_offset_ = buffer_offset_; + last_updated_operation_num_ = next_operation_num_; if (next_operation_num_ < num_total_operations_) { size_t partition_index = current_partition_; - while (next_operation_num_ >= acc_num_operations_[partition_index]) + while (next_operation_num_ >= acc_num_operations_[partition_index]) { partition_index++; + } const size_t partition_operation_num = next_operation_num_ - (partition_index ? acc_num_operations_[partition_index - 1] : 0); @@ -2034,6 +1450,14 @@ bool DeltaPerformer::CheckpointUpdateProgress(bool force) { TEST_AND_RETURN_FALSE( prefs_->SetInt64(kPrefsUpdateStateNextDataLength, 0)); } + if (partition_writer_) { + partition_writer_->CheckpointUpdateProgress(GetPartitionOperationNum()); + } else { + CHECK_EQ(next_operation_num_, num_total_operations_) + << "Partition writer is null, we are expected to finish all " + "operations: " + << next_operation_num_ << "/" << num_total_operations_; + } } TEST_AND_RETURN_FALSE( prefs_->SetInt64(kPrefsUpdateStateNextOperation, next_operation_num_)); @@ -2101,4 +1525,26 @@ bool DeltaPerformer::PrimeUpdateState() { return true; } +bool DeltaPerformer::IsDynamicPartition(const std::string& part_name, + uint32_t slot) { + return boot_control_->GetDynamicPartitionControl()->IsDynamicPartition( + part_name, slot); +} + +std::unique_ptr<PartitionWriter> DeltaPerformer::CreatePartitionWriter( + const PartitionUpdate& partition_update, + const InstallPlan::Partition& install_part, + DynamicPartitionControlInterface* dynamic_control, + size_t block_size, + bool is_interactive, + bool is_dynamic_partition) { + return partition_writer::CreatePartitionWriter( + partition_update, + install_part, + dynamic_control, + block_size_, + interactive_, + IsDynamicPartition(install_part.name, install_plan_->target_slot)); +} + } // namespace chromeos_update_engine diff --git a/payload_consumer/delta_performer.h b/payload_consumer/delta_performer.h index 01fcc5c9..c54316bd 100644 --- a/payload_consumer/delta_performer.h +++ b/payload_consumer/delta_performer.h @@ -35,6 +35,7 @@ #include "update_engine/payload_consumer/file_descriptor.h" #include "update_engine/payload_consumer/file_writer.h" #include "update_engine/payload_consumer/install_plan.h" +#include "update_engine/payload_consumer/partition_writer.h" #include "update_engine/payload_consumer/payload_metadata.h" #include "update_engine/payload_consumer/payload_verifier.h" #include "update_engine/update_metadata.pb.h" @@ -48,7 +49,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 @@ -78,7 +78,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. @@ -100,10 +102,6 @@ class DeltaPerformer : public FileWriter { // work. Returns whether the required file descriptors were successfully open. bool OpenCurrentPartition(); - // Attempt to open the error-corrected device for the current partition. - // Returns whether the operation succeeded. - bool OpenCurrentECCPartition(); - // Closes the current partition file descriptors if open. Returns 0 on success // or -errno on error. int CloseCurrentPartition(); @@ -172,9 +170,11 @@ class DeltaPerformer : public FileWriter { // Return true if header parsing is finished and no errors occurred. bool IsHeaderParsed() const; - // Returns the delta minor version. If this value is defined in the manifest, - // it returns that value, otherwise it returns the default value. - uint32_t GetMinorVersion() const; + // Checkpoints the update progress into persistent storage to allow this + // update attempt to be resumed after reboot. + // If |force| is false, checkpoint may be throttled. + // Exposed for testing purposes. + bool CheckpointUpdateProgress(bool force); // Compare |calculated_hash| with source hash in |operation|, return false and // dump hash and set |error| if don't match. @@ -202,14 +202,33 @@ class DeltaPerformer : public FileWriter { const std::string& update_check_response_hash, uint64_t* required_size); + protected: + // Exposed as virtual for testing purposes. + virtual std::unique_ptr<PartitionWriter> CreatePartitionWriter( + const PartitionUpdate& partition_update, + const InstallPlan::Partition& install_part, + DynamicPartitionControlInterface* dynamic_control, + size_t block_size, + bool is_interactive, + bool is_dynamic_partition); + + // return true if it has been long enough and a checkpoint should be saved. + // Exposed for unittest purposes. + virtual bool ShouldCheckpoint(); + private: friend class DeltaPerformerTest; friend class DeltaPerformerIntegrationTest; FRIEND_TEST(DeltaPerformerTest, BrilloMetadataSignatureSizeTest); FRIEND_TEST(DeltaPerformerTest, BrilloParsePayloadMetadataTest); - FRIEND_TEST(DeltaPerformerTest, ChooseSourceFDTest); FRIEND_TEST(DeltaPerformerTest, UsePublicKeyFromResponse); + // Obtain the operation index for current partition. If all operations for + // current partition is are finished, return # of operations. This is mostly + // intended to be used by CheckpointUpdateProgress, where partition writer + // needs to know the current operation number to properly checkpoint update. + size_t GetPartitionOperationNum(); + // Parse and move the update instructions of all partitions into our local // |partitions_| variable based on the version of the payload. Requires the // manifest to be parsed and valid. @@ -254,8 +273,6 @@ class DeltaPerformer : public FileWriter { // set even if it fails. bool PerformReplaceOperation(const InstallOperation& operation); bool PerformZeroOrDiscardOperation(const InstallOperation& operation); - bool PerformMoveOperation(const InstallOperation& operation); - bool PerformBsdiffOperation(const InstallOperation& operation); bool PerformSourceCopyOperation(const InstallOperation& operation, ErrorCode* error); bool PerformSourceBsdiffOperation(const InstallOperation& operation, @@ -263,18 +280,6 @@ class DeltaPerformer : public FileWriter { bool PerformPuffDiffOperation(const InstallOperation& operation, ErrorCode* error); - // For a given operation, choose the source fd to be used (raw device or error - // correction device) based on the source operation hash. - // Returns nullptr if the source hash mismatch cannot be corrected, and set - // the |error| accordingly. - FileDescriptorPtr ChooseSourceFD(const InstallOperation& operation, - ErrorCode* error); - - // Extracts the payload signature message from the blob on the |operation| if - // the offset matches the one specified by the manifest. Returns whether the - // signature was extracted. - bool ExtractSignatureMessageFromOperation(const InstallOperation& operation); - // Extracts the payload signature message from the current |buffer_| if the // offset matches the one specified by the manifest. Returns whether the // signature was extracted. @@ -287,11 +292,6 @@ class DeltaPerformer : public FileWriter { // accordingly. void DiscardBuffer(bool do_advance_offset, size_t signed_hash_buffer_size); - // Checkpoints the update progress into persistent storage to allow this - // update attempt to be resumed after reboot. - // If |force| is false, checkpoint may be throttled. - bool CheckpointUpdateProgress(bool force); - // Primes the required update state. Returns true if the update state was // successfully initialized to a saved resume state or if the update is a new // update. Returns false otherwise. @@ -315,6 +315,18 @@ class DeltaPerformer : public FileWriter { // 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; + + // Check if partition `part_name` is a dynamic partition. + bool IsDynamicPartition(const std::string& part_name, uint32_t slot); + // Update Engine preference store. PrefsInterface* prefs_; @@ -332,34 +344,6 @@ class DeltaPerformer : public FileWriter { // Pointer to the current payload in install_plan_.payloads. InstallPlan::Payload* payload_{nullptr}; - // File descriptor of the source partition. Only set while updating a - // partition when using a delta payload. - FileDescriptorPtr source_fd_{nullptr}; - - // File descriptor of the error corrected source partition. Only set while - // updating partition using a delta payload for a partition where error - // correction is available. The size of the error corrected device is smaller - // than the underlying raw device, since it doesn't include the error - // correction blocks. - FileDescriptorPtr source_ecc_fd_{nullptr}; - - // The total number of operations that failed source hash verification but - // passed after falling back to the error-corrected |source_ecc_fd_| device. - uint64_t source_ecc_recovered_failures_{0}; - - // Whether opening the current partition as an error-corrected device failed. - // Used to avoid re-opening the same source partition if it is not actually - // error corrected. - bool source_ecc_open_failure_{false}; - - // File descriptor of the target partition. Only set while performing the - // operations of a given partition. - FileDescriptorPtr target_fd_{nullptr}; - - // Paths the |source_fd_| and |target_fd_| refer to. - std::string source_path_; - std::string target_path_; - PayloadMetadata payload_metadata_; // Parsed manifest. Set after enough bytes to parse the manifest were @@ -380,28 +364,28 @@ class DeltaPerformer : public FileWriter { // otherwise 0. size_t num_total_operations_{0}; - // The list of partitions to update as found in the manifest major version 2. - // When parsing an older manifest format, the information is converted over to - // this format instead. + // The list of partitions to update as found in the manifest major + // version 2. When parsing an older manifest format, the information is + // converted over to this format instead. std::vector<PartitionUpdate> partitions_; // Index in the list of partitions (|partitions_| member) of the current // partition being processed. size_t current_partition_{0}; - // Index of the next operation to perform in the manifest. The index is linear - // on the total number of operation on the manifest. + // Index of the next operation to perform in the manifest. The index is + // linear on the total number of operation on the manifest. size_t next_operation_num_{0}; // A buffer used for accumulating downloaded data. Initially, it stores the - // payload metadata; once that's downloaded and parsed, it stores data for the - // next update operation. + // payload metadata; once that's downloaded and parsed, it stores data for + // the next update operation. brillo::Blob buffer_; // Offset of buffer_ in the binary blobs section of the update. uint64_t buffer_offset_{0}; - // Last |buffer_offset_| value updated as part of the progress update. - uint64_t last_updated_buffer_offset_{std::numeric_limits<uint64_t>::max()}; + // Last |next_operation_num_| value updated as part of the progress update. + uint64_t last_updated_operation_num_{std::numeric_limits<uint64_t>::max()}; // The block size (parsed from the manifest). uint32_t block_size_{0}; @@ -437,8 +421,9 @@ class DeltaPerformer : public FileWriter { // If |true|, the update is user initiated (vs. periodic update checks). bool interactive_{false}; - // The timeout after which we should force emitting a progress log (constant), - // and the actual point in time for the next forced log to be emitted. + // The timeout after which we should force emitting a progress log + // (constant), and the actual point in time for the next forced log to be + // emitted. const base::TimeDelta forced_progress_log_wait_{ base::TimeDelta::FromSeconds(kProgressLogTimeoutSeconds)}; base::TimeTicks forced_progress_log_time_; @@ -449,6 +434,8 @@ class DeltaPerformer : public FileWriter { base::TimeDelta::FromSeconds(kCheckpointFrequencySeconds)}; base::TimeTicks update_checkpoint_time_; + std::unique_ptr<PartitionWriter> partition_writer_; + DISALLOW_COPY_AND_ASSIGN(DeltaPerformer); }; diff --git a/payload_consumer/delta_performer_fuzzer.cc b/payload_consumer/delta_performer_fuzzer.cc new file mode 100644 index 00000000..0ce50819 --- /dev/null +++ b/payload_consumer/delta_performer_fuzzer.cc @@ -0,0 +1,105 @@ +// +// Copyright 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 <string> + +#include <base/logging.h> +#include <fuzzer/FuzzedDataProvider.h> + +#include "update_engine/common/download_action.h" +#include "update_engine/common/fake_boot_control.h" +#include "update_engine/common/fake_hardware.h" +#include "update_engine/common/prefs.h" +#include "update_engine/payload_consumer/delta_performer.h" +#include "update_engine/payload_consumer/install_plan.h" + +namespace chromeos_update_engine { + +class FakeDownloadActionDelegate : public DownloadActionDelegate { + public: + FakeDownloadActionDelegate() = default; + ~FakeDownloadActionDelegate() = default; + + // DownloadActionDelegate overrides; + void BytesReceived(uint64_t bytes_progressed, + uint64_t bytes_received, + uint64_t total) override{}; + + bool ShouldCancel(ErrorCode* cancel_reason) override { return false; }; + + void DownloadComplete() override{}; + + DISALLOW_COPY_AND_ASSIGN(FakeDownloadActionDelegate); +}; + +void FuzzDeltaPerformer(const uint8_t* data, size_t size) { + MemoryPrefs prefs; + FakeBootControl boot_control; + FakeHardware hardware; + FakeDownloadActionDelegate download_action_delegate; + + FuzzedDataProvider data_provider(data, size); + + InstallPlan install_plan{ + .target_slot = 1, + .partitions = {InstallPlan::Partition{ + .source_path = "/dev/zero", + .source_size = 4096, + .target_path = "/dev/null", + .target_size = 4096, + }}, + .hash_checks_mandatory = true, + }; + + InstallPlan::Payload payload{ + .size = data_provider.ConsumeIntegralInRange<uint64_t>(0, 10000), + .metadata_size = data_provider.ConsumeIntegralInRange<uint64_t>(0, 1000), + .hash = data_provider.ConsumeBytes<uint8_t>(32), + .type = static_cast<InstallPayloadType>( + data_provider.ConsumeIntegralInRange(0, 3)), + .already_applied = data_provider.ConsumeBool(), + }; + + DeltaPerformer performer(&prefs, + &boot_control, + &hardware, + &download_action_delegate, + &install_plan, + &payload, + data_provider.ConsumeBool()); + do { + auto chunk_size = data_provider.ConsumeIntegralInRange<size_t>(0, 100); + auto data = data_provider.ConsumeBytes<uint8_t>(chunk_size); + if (!performer.Write(data.data(), data.size())) + break; + } while (data_provider.remaining_bytes() > 0); +} + +} // namespace chromeos_update_engine + +class Environment { + public: + Environment() { logging::SetMinLogLevel(logging::LOG_FATAL); } +}; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size > 1000000) { + return 0; + } + + static Environment env; + chromeos_update_engine::FuzzDeltaPerformer(data, size); + return 0; +} diff --git a/payload_consumer/delta_performer_integration_test.cc b/payload_consumer/delta_performer_integration_test.cc index 4797137b..4fab9751 100644 --- a/payload_consumer/delta_performer_integration_test.cc +++ b/payload_consumer/delta_performer_integration_test.cc @@ -25,8 +25,10 @@ #include <base/files/file_path.h> #include <base/files/file_util.h> +#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> @@ -34,10 +36,13 @@ #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/hardware_interface.h" +#include "update_engine/common/mock_download_action.h" #include "update_engine/common/mock_prefs.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" -#include "update_engine/payload_consumer/mock_download_action.h" +#include "update_engine/payload_consumer/install_plan.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" @@ -47,13 +52,18 @@ namespace chromeos_update_engine { +using std::list; using std::string; +using std::unique_ptr; using std::vector; using test_utils::GetBuildArtifactsPath; using test_utils::kRandomString; using test_utils::ScopedLoopMounter; using test_utils::System; using testing::_; +using testing::IsEmpty; +using testing::NiceMock; +using testing::Not; using testing::Return; extern const char* kUnittestPrivateKeyPath; @@ -71,21 +81,24 @@ static const uint8_t kNewData[] = {'T', 'h', 'i', 's', ' ', 'i', 's', ' ', namespace { struct DeltaState { - string a_img; - string b_img; - string result_img; + unique_ptr<ScopedTempFile> a_img; + unique_ptr<ScopedTempFile> b_img; + unique_ptr<ScopedTempFile> result_img; size_t image_size; - string delta_path; + unique_ptr<ScopedTempFile> delta_file; + // The in-memory copy of delta file. + brillo::Blob delta; uint64_t metadata_size; + uint32_t metadata_signature_size; - string old_kernel; + unique_ptr<ScopedTempFile> old_kernel; brillo::Blob old_kernel_data; - string new_kernel; + unique_ptr<ScopedTempFile> new_kernel; brillo::Blob new_kernel_data; - string result_kernel; + unique_ptr<ScopedTempFile> result_kernel; brillo::Blob result_kernel_data; size_t kernel_size; @@ -93,9 +106,6 @@ struct DeltaState { // the DeltaPerformer. InstallPlan install_plan; - // The in-memory copy of delta file. - brillo::Blob delta; - // Mock and fake instances used by the delta performer. FakeBootControl fake_boot_control_; FakeHardware fake_hardware_; @@ -122,7 +132,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, + 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, @@ -187,15 +231,18 @@ static void SignGeneratedPayload(const string& payload_path, size_t signature_size; ASSERT_TRUE(PayloadSigner::GetMaximumSignatureSize(private_key_path, &signature_size)); - brillo::Blob hash; + brillo::Blob metadata_hash, payload_hash; ASSERT_TRUE(PayloadSigner::HashPayloadForSigning( - payload_path, {signature_size}, &hash, nullptr)); - brillo::Blob signature; - ASSERT_TRUE(PayloadSigner::SignHash(hash, private_key_path, &signature)); + payload_path, {signature_size}, &payload_hash, &metadata_hash)); + brillo::Blob metadata_signature, payload_signature; + ASSERT_TRUE(PayloadSigner::SignHash( + payload_hash, private_key_path, &payload_signature)); + ASSERT_TRUE(PayloadSigner::SignHash( + metadata_hash, private_key_path, &metadata_signature)); ASSERT_TRUE(PayloadSigner::AddSignatureToPayload(payload_path, {signature_size}, - {signature}, - {}, + {payload_signature}, + {metadata_signature}, payload_path, out_metadata_size)); EXPECT_TRUE(PayloadSigner::VerifySignedPayload( @@ -216,39 +263,56 @@ static void SignGeneratedShellPayloadWithKeys( } string signature_size_string = base::JoinString(signature_size_strings, ":"); - test_utils::ScopedTempFile hash_file("hash.XXXXXX"); + 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", + "%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()))); + hash_file.path().c_str(), + metadata_hash_file.path().c_str()))); // Sign the hash with all private keys. - vector<test_utils::ScopedTempFile> sig_files; - vector<string> sig_file_paths; + list<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)); + sig_files.emplace_back("signature.XXXXXX"); + ASSERT_TRUE( + test_utils::WriteFileVector(sig_files.back().path(), signature)); + sig_file_paths.push_back(sig_files.back().path()); + + 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)); + + metadata_sig_files.emplace_back("metadata_signature.XXXXXX"); + ASSERT_TRUE(test_utils::WriteFileVector(metadata_sig_files.back().path(), + metadata_signature)); + metadata_sig_file_paths.push_back(metadata_sig_files.back().path()); } 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 -out_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", @@ -314,7 +378,7 @@ static void SignGeneratedShellPayload(SignatureTest signature_test, GetBuildArtifactsPath(kUnittestPrivateKey2Path)); } - std::string public_key; + string public_key; if (signature_test == kSignatureGeneratedShellRotateCl2) { public_key = GetBuildArtifactsPath(kUnittestPublicKey2Path); } else if (signature_test == kSignatureGeneratedShellECKey) { @@ -330,49 +394,27 @@ static void SignGeneratedShellPayload(SignatureTest signature_test, static void GenerateDeltaFile(bool full_kernel, bool full_rootfs, - bool noop, ssize_t chunk_size, SignatureTest signature_test, DeltaState* state, uint32_t minor_version) { - EXPECT_TRUE(utils::MakeTempFile("a_img.XXXXXX", &state->a_img, nullptr)); - EXPECT_TRUE(utils::MakeTempFile("b_img.XXXXXX", &state->b_img, nullptr)); + state->a_img.reset(new ScopedTempFile("a_img.XXXXXX")); + state->b_img.reset(new ScopedTempFile("b_img.XXXXXX")); // result_img is used in minor version 2. Instead of applying the update // in-place on A, we apply it to a new image, result_img. - EXPECT_TRUE( - utils::MakeTempFile("result_img.XXXXXX", &state->result_img, nullptr)); + state->result_img.reset(new ScopedTempFile("result_img.XXXXXX")); EXPECT_TRUE( base::CopyFile(GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"), - base::FilePath(state->a_img))); - - state->image_size = utils::FileSize(state->a_img); - - // Create ImageInfo A & B - ImageInfo old_image_info; - ImageInfo new_image_info; - - if (!full_rootfs) { - old_image_info.set_channel("src-channel"); - old_image_info.set_board("src-board"); - old_image_info.set_version("src-version"); - old_image_info.set_key("src-key"); - old_image_info.set_build_channel("src-build-channel"); - old_image_info.set_build_version("src-build-version"); - } + base::FilePath(state->a_img->path()))); - new_image_info.set_channel("test-channel"); - new_image_info.set_board("test-board"); - new_image_info.set_version("test-version"); - new_image_info.set_key("test-key"); - new_image_info.set_build_channel("test-build-channel"); - new_image_info.set_build_version("test-build-version"); + state->image_size = utils::FileSize(state->a_img->path()); // Make some changes to the A image. { string a_mnt; - ScopedLoopMounter b_mounter(state->a_img, &a_mnt, 0); + ScopedLoopMounter b_mounter(state->a_img->path(), &a_mnt, 0); brillo::Blob hardtocompress; while (hardtocompress.size() < 3 * kBlockSize) { @@ -407,32 +449,29 @@ static void GenerateDeltaFile(bool full_kernel, ones.size())); } - if (noop) { - EXPECT_TRUE(base::CopyFile(base::FilePath(state->a_img), - base::FilePath(state->b_img))); - old_image_info = new_image_info; - } else { - if (minor_version == kSourceMinorPayloadVersion) { - // Create a result image with image_size bytes of garbage. - brillo::Blob ones(state->image_size, 0xff); - EXPECT_TRUE(utils::WriteFile( - state->result_img.c_str(), ones.data(), ones.size())); - EXPECT_EQ(utils::FileSize(state->a_img), - utils::FileSize(state->result_img)); - } - - EXPECT_TRUE( - base::CopyFile(GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"), - base::FilePath(state->b_img))); + // Create a result image with image_size bytes of garbage. + brillo::Blob ones(state->image_size, 0xff); + EXPECT_TRUE(utils::WriteFile( + state->result_img->path().c_str(), ones.data(), ones.size())); + EXPECT_EQ(utils::FileSize(state->a_img->path()), + utils::FileSize(state->result_img->path())); + EXPECT_TRUE( + base::CopyFile(GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"), + base::FilePath(state->b_img->path()))); + { // Make some changes to the B image. string b_mnt; - ScopedLoopMounter b_mounter(state->b_img, &b_mnt, 0); + ScopedLoopMounter b_mounter(state->b_img->path(), &b_mnt, 0); base::FilePath mnt_path(b_mnt); EXPECT_TRUE(base::CopyFile(mnt_path.Append("regular-small"), mnt_path.Append("regular-small2"))); +#if BASE_VER < 800000 EXPECT_TRUE(base::DeleteFile(mnt_path.Append("regular-small"), false)); +#else + EXPECT_TRUE(base::DeleteFile(mnt_path.Append("regular-small"))); +#endif EXPECT_TRUE(base::Move(mnt_path.Append("regular-small2"), mnt_path.Append("regular-small"))); EXPECT_TRUE( @@ -459,7 +498,11 @@ static void GenerateDeltaFile(bool full_kernel, EXPECT_TRUE(base::Move(mnt_path.Append("tmp"), mnt_path.Append("link-hard-regular-16k"))); +#if BASE_VER < 800000 EXPECT_TRUE(base::DeleteFile(mnt_path.Append("link-short_symlink"), false)); +#else + EXPECT_TRUE(base::DeleteFile(mnt_path.Append("link-short_symlink"))); +#endif EXPECT_TRUE(test_utils::WriteFileString( mnt_path.Append("link-short_symlink").value(), "foobar")); @@ -475,18 +518,9 @@ static void GenerateDeltaFile(bool full_kernel, hardtocompress.size())); } - string old_kernel; - EXPECT_TRUE( - utils::MakeTempFile("old_kernel.XXXXXX", &state->old_kernel, nullptr)); - - string new_kernel; - EXPECT_TRUE( - utils::MakeTempFile("new_kernel.XXXXXX", &state->new_kernel, nullptr)); - - string result_kernel; - EXPECT_TRUE(utils::MakeTempFile( - "result_kernel.XXXXXX", &state->result_kernel, nullptr)); - + state->old_kernel.reset(new ScopedTempFile("old_kernel.XXXXXX")); + state->new_kernel.reset(new ScopedTempFile("new_kernel.XXXXXX")); + state->result_kernel.reset(new ScopedTempFile("result_kernel.XXXXXX")); state->kernel_size = kDefaultKernelSize; state->old_kernel_data.resize(kDefaultKernelSize); state->new_kernel_data.resize(state->old_kernel_data.size()); @@ -499,23 +533,18 @@ static void GenerateDeltaFile(bool full_kernel, std::copy( std::begin(kNewData), std::end(kNewData), state->new_kernel_data.begin()); - if (noop) { - state->old_kernel_data = state->new_kernel_data; - } - // Write kernels to disk - EXPECT_TRUE(utils::WriteFile(state->old_kernel.c_str(), + EXPECT_TRUE(utils::WriteFile(state->old_kernel->path().c_str(), state->old_kernel_data.data(), state->old_kernel_data.size())); - EXPECT_TRUE(utils::WriteFile(state->new_kernel.c_str(), + EXPECT_TRUE(utils::WriteFile(state->new_kernel->path().c_str(), state->new_kernel_data.data(), state->new_kernel_data.size())); - EXPECT_TRUE(utils::WriteFile(state->result_kernel.c_str(), + EXPECT_TRUE(utils::WriteFile(state->result_kernel->path().c_str(), state->result_kernel_data.data(), state->result_kernel_data.size())); - EXPECT_TRUE(utils::MakeTempFile("delta.XXXXXX", &state->delta_path, nullptr)); - LOG(INFO) << "delta path: " << state->delta_path; + state->delta_file.reset(new ScopedTempFile("delta.XXXXXX")); { const string private_key = signature_test == kSignatureGenerator @@ -526,15 +555,15 @@ static void GenerateDeltaFile(bool full_kernel, payload_config.is_delta = !full_rootfs; payload_config.hard_chunk_size = chunk_size; payload_config.rootfs_partition_size = kRootFSPartitionSize; - payload_config.version.major = kChromeOSMajorPayloadVersion; + payload_config.version.major = kBrilloMajorPayloadVersion; payload_config.version.minor = minor_version; if (!full_rootfs) { payload_config.source.partitions.emplace_back(kPartitionNameRoot); payload_config.source.partitions.emplace_back(kPartitionNameKernel); - payload_config.source.partitions.front().path = state->a_img; + payload_config.source.partitions.front().path = state->a_img->path(); if (!full_kernel) - payload_config.source.partitions.back().path = state->old_kernel; - payload_config.source.image_info = old_image_info; + payload_config.source.partitions.back().path = + state->old_kernel->path(); EXPECT_TRUE(payload_config.source.LoadImageSize()); for (PartitionConfig& part : payload_config.source.partitions) EXPECT_TRUE(part.OpenFilesystem()); @@ -544,29 +573,30 @@ static void GenerateDeltaFile(bool full_kernel, payload_config.hard_chunk_size = 1024 * 1024; } payload_config.target.partitions.emplace_back(kPartitionNameRoot); - payload_config.target.partitions.back().path = state->b_img; + payload_config.target.partitions.back().path = state->b_img->path(); payload_config.target.partitions.emplace_back(kPartitionNameKernel); - payload_config.target.partitions.back().path = state->new_kernel; - payload_config.target.image_info = new_image_info; + payload_config.target.partitions.back().path = state->new_kernel->path(); EXPECT_TRUE(payload_config.target.LoadImageSize()); for (PartitionConfig& part : payload_config.target.partitions) EXPECT_TRUE(part.OpenFilesystem()); EXPECT_TRUE(payload_config.Validate()); - EXPECT_TRUE(GenerateUpdatePayloadFile( - payload_config, state->delta_path, private_key, &state->metadata_size)); + EXPECT_TRUE(GenerateUpdatePayloadFile(payload_config, + state->delta_file->path(), + private_key, + &state->metadata_size)); } // Extend the "partitions" holding the file system a bit. EXPECT_EQ(0, - HANDLE_EINTR(truncate(state->a_img.c_str(), + HANDLE_EINTR(truncate(state->a_img->path().c_str(), state->image_size + 1024 * 1024))); EXPECT_EQ(static_cast<off_t>(state->image_size + 1024 * 1024), - utils::FileSize(state->a_img)); + utils::FileSize(state->a_img->path())); EXPECT_EQ(0, - HANDLE_EINTR(truncate(state->b_img.c_str(), + HANDLE_EINTR(truncate(state->b_img->path().c_str(), state->image_size + 1024 * 1024))); EXPECT_EQ(static_cast<off_t>(state->image_size + 1024 * 1024), - utils::FileSize(state->b_img)); + utils::FileSize(state->b_img->path())); if (signature_test == kSignatureGeneratedPlaceholder || signature_test == kSignatureGeneratedPlaceholderMismatch) { @@ -575,13 +605,13 @@ static void GenerateDeltaFile(bool full_kernel, GetBuildArtifactsPath(kUnittestPrivateKeyPath), &signature_size)); LOG(INFO) << "Inserting placeholder signature."; ASSERT_TRUE(InsertSignaturePlaceholder( - signature_size, state->delta_path, &state->metadata_size)); + signature_size, state->delta_file->path(), &state->metadata_size)); if (signature_test == kSignatureGeneratedPlaceholderMismatch) { signature_size -= 1; LOG(INFO) << "Inserting mismatched placeholder signature."; ASSERT_FALSE(InsertSignaturePlaceholder( - signature_size, state->delta_path, &state->metadata_size)); + signature_size, state->delta_file->path(), &state->metadata_size)); return; } } @@ -593,19 +623,18 @@ static void GenerateDeltaFile(bool full_kernel, // reflect the new size after adding the signature operation to the // manifest. LOG(INFO) << "Signing payload."; - SignGeneratedPayload(state->delta_path, &state->metadata_size); + SignGeneratedPayload(state->delta_file->path(), &state->metadata_size); } else if (signature_test == kSignatureGeneratedShell || signature_test == kSignatureGeneratedShellECKey || signature_test == kSignatureGeneratedShellBadKey || signature_test == kSignatureGeneratedShellRotateCl1 || signature_test == kSignatureGeneratedShellRotateCl2) { - SignGeneratedShellPayload(signature_test, state->delta_path); + SignGeneratedShellPayload(signature_test, state->delta_file->path()); } } static void ApplyDeltaFile(bool full_kernel, bool full_rootfs, - bool noop, SignatureTest signature_test, DeltaState* state, bool hash_checks_mandatory, @@ -614,11 +643,14 @@ static void ApplyDeltaFile(bool full_kernel, uint32_t minor_version) { // Check the metadata. { - EXPECT_TRUE(utils::ReadFile(state->delta_path, &state->delta)); + EXPECT_TRUE(utils::ReadFile(state->delta_file->path(), &state->delta)); PayloadMetadata payload_metadata; EXPECT_TRUE(payload_metadata.ParsePayloadHeader(state->delta)); state->metadata_size = payload_metadata.GetMetadataSize(); LOG(INFO) << "Metadata size: " << state->metadata_size; + state->metadata_signature_size = + payload_metadata.GetMetadataSignatureSize(); + LOG(INFO) << "Metadata signature size: " << state->metadata_signature_size; DeltaArchiveManifest manifest; EXPECT_TRUE(payload_metadata.GetManifest(state->delta, &manifest)); @@ -630,7 +662,8 @@ static void ApplyDeltaFile(bool full_kernel, EXPECT_TRUE(manifest.has_signatures_size()); Signatures sigs_message; EXPECT_TRUE(sigs_message.ParseFromArray( - &state->delta[state->metadata_size + manifest.signatures_offset()], + &state->delta[state->metadata_size + state->metadata_signature_size + + manifest.signatures_offset()], manifest.signatures_size())); if (signature_test == kSignatureGeneratedShellRotateCl1 || signature_test == kSignatureGeneratedShellRotateCl2) @@ -653,68 +686,63 @@ static void ApplyDeltaFile(bool full_kernel, EXPECT_FALSE(signature.data().empty()); } - if (noop) { - EXPECT_EQ(0, manifest.install_operations_size()); - EXPECT_EQ(1, manifest.kernel_install_operations_size()); - } - + // TODO(ahassani): Make |DeltaState| into a partition list kind of struct + // instead of hardcoded kernel/rootfs so its cleaner and we can make the + // following code into a helper function instead. + const auto& kernel_part = *std::find_if( + manifest.partitions().begin(), + manifest.partitions().end(), + [](const PartitionUpdate& partition) { + return partition.partition_name() == kPartitionNameKernel; + }); if (full_kernel) { - EXPECT_FALSE(manifest.has_old_kernel_info()); + EXPECT_FALSE(kernel_part.has_old_partition_info()); } else { EXPECT_EQ(state->old_kernel_data.size(), - manifest.old_kernel_info().size()); - EXPECT_FALSE(manifest.old_kernel_info().hash().empty()); + kernel_part.old_partition_info().size()); + EXPECT_FALSE(kernel_part.old_partition_info().hash().empty()); } - - EXPECT_EQ(manifest.new_image_info().channel(), "test-channel"); - EXPECT_EQ(manifest.new_image_info().board(), "test-board"); - EXPECT_EQ(manifest.new_image_info().version(), "test-version"); - EXPECT_EQ(manifest.new_image_info().key(), "test-key"); - EXPECT_EQ(manifest.new_image_info().build_channel(), "test-build-channel"); - EXPECT_EQ(manifest.new_image_info().build_version(), "test-build-version"); - - if (!full_rootfs) { - if (noop) { - EXPECT_EQ(manifest.old_image_info().channel(), "test-channel"); - EXPECT_EQ(manifest.old_image_info().board(), "test-board"); - EXPECT_EQ(manifest.old_image_info().version(), "test-version"); - EXPECT_EQ(manifest.old_image_info().key(), "test-key"); - EXPECT_EQ(manifest.old_image_info().build_channel(), - "test-build-channel"); - EXPECT_EQ(manifest.old_image_info().build_version(), - "test-build-version"); - } else { - EXPECT_EQ(manifest.old_image_info().channel(), "src-channel"); - EXPECT_EQ(manifest.old_image_info().board(), "src-board"); - EXPECT_EQ(manifest.old_image_info().version(), "src-version"); - EXPECT_EQ(manifest.old_image_info().key(), "src-key"); - EXPECT_EQ(manifest.old_image_info().build_channel(), - "src-build-channel"); - EXPECT_EQ(manifest.old_image_info().build_version(), - "src-build-version"); - } - } - + EXPECT_EQ(state->new_kernel_data.size(), + kernel_part.new_partition_info().size()); + EXPECT_FALSE(kernel_part.new_partition_info().hash().empty()); + + const auto& rootfs_part = + *std::find_if(manifest.partitions().begin(), + manifest.partitions().end(), + [](const PartitionUpdate& partition) { + return partition.partition_name() == kPartitionNameRoot; + }); if (full_rootfs) { - EXPECT_FALSE(manifest.has_old_rootfs_info()); - EXPECT_FALSE(manifest.has_old_image_info()); - EXPECT_TRUE(manifest.has_new_image_info()); + EXPECT_FALSE(rootfs_part.has_old_partition_info()); } else { - EXPECT_EQ(state->image_size, manifest.old_rootfs_info().size()); - EXPECT_FALSE(manifest.old_rootfs_info().hash().empty()); + EXPECT_FALSE(rootfs_part.old_partition_info().hash().empty()); } - - EXPECT_EQ(state->new_kernel_data.size(), manifest.new_kernel_info().size()); - EXPECT_EQ(state->image_size, manifest.new_rootfs_info().size()); - - EXPECT_FALSE(manifest.new_kernel_info().hash().empty()); - EXPECT_FALSE(manifest.new_rootfs_info().hash().empty()); + EXPECT_FALSE(rootfs_part.new_partition_info().hash().empty()); } - MockPrefs prefs; + NiceMock<MockPrefs> prefs; + ON_CALL(prefs, SetInt64(kPrefsManifestMetadataSize, -1)) + .WillByDefault(Return(true)); + ON_CALL(prefs, SetInt64(kPrefsUpdateCheckResponseHash, -1)) + .WillByDefault(Return(true)); + ON_CALL(prefs, GetString(kPrefsUpdateCheckResponseHash, _)) + .WillByDefault(Return(true)); + ON_CALL(prefs, GetString(kPrefsDynamicPartitionMetadataUpdated, _)) + .WillByDefault(Return(true)); + + // Set default expectation to ignore uninteresting calls to + // SetString/SetInt64. When starting an update delta_performer might reset + // update checkpoints, which results in a lot of calls with empty string or + // integer -1. Ignore these. + EXPECT_CALL(prefs, SetString(_, IsEmpty())).WillRepeatedly(Return(true)); + EXPECT_CALL(prefs, SetInt64(_, -1)).WillRepeatedly(Return(true)); + EXPECT_CALL(prefs, SetInt64(_, 0)).WillRepeatedly(Return(true)); + EXPECT_CALL(prefs, SetInt64(kPrefsManifestMetadataSize, state->metadata_size)) .WillOnce(Return(true)); - EXPECT_CALL(prefs, SetInt64(kPrefsManifestSignatureSize, 0)) + EXPECT_CALL( + prefs, + SetInt64(kPrefsManifestSignatureSize, state->metadata_signature_size)) .WillOnce(Return(true)); EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextOperation, _)) .WillRepeatedly(Return(true)); @@ -730,9 +758,15 @@ static void ApplyDeltaFile(bool full_kernel, .WillRepeatedly(Return(true)); 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, _)) - .WillOnce(Return(true)); + EXPECT_CALL(prefs, + SetString(kPrefsUpdateStateSignatureBlob, Not(IsEmpty()))) + .WillRepeatedly(Return(true)); } EXPECT_CALL(state->mock_delegate_, ShouldCancel(_)) @@ -741,7 +775,8 @@ static void ApplyDeltaFile(bool full_kernel, // Update the A image in place. InstallPlan* install_plan = &state->install_plan; install_plan->hash_checks_mandatory = hash_checks_mandatory; - install_plan->payloads = {{.metadata_size = state->metadata_size, + install_plan->payloads = {{.size = state->delta.size(), + .metadata_size = state->metadata_size, .type = (full_kernel && full_rootfs) ? InstallPayloadType::kFull : InstallPayloadType::kDelta}}; @@ -779,34 +814,26 @@ static void ApplyDeltaFile(bool full_kernel, (*performer)->set_public_key_path(public_key_path); (*performer)->set_update_certificates_path(""); - EXPECT_EQ(static_cast<off_t>(state->image_size), - HashCalculator::RawHashOfFile( - state->a_img, state->image_size, &root_part.source_hash)); + EXPECT_EQ( + static_cast<off_t>(state->image_size), + HashCalculator::RawHashOfFile( + state->a_img->path(), state->image_size, &root_part.source_hash)); EXPECT_TRUE(HashCalculator::RawHashOfData(state->old_kernel_data, &kernel_part.source_hash)); // The partitions should be empty before DeltaPerformer. install_plan->partitions.clear(); - // With minor version 2, we want the target to be the new image, result_img, - // but with version 1, we want to update A in place. - string target_root, target_kernel; - if (minor_version == kSourceMinorPayloadVersion) { - target_root = state->result_img; - target_kernel = state->result_kernel; - } else { - target_root = state->a_img; - target_kernel = state->old_kernel; - } - - state->fake_boot_control_.SetPartitionDevice( - kPartitionNameRoot, install_plan->source_slot, state->a_img); state->fake_boot_control_.SetPartitionDevice( - kPartitionNameKernel, install_plan->source_slot, state->old_kernel); + kPartitionNameRoot, install_plan->source_slot, state->a_img->path()); + state->fake_boot_control_.SetPartitionDevice(kPartitionNameKernel, + install_plan->source_slot, + state->old_kernel->path()); state->fake_boot_control_.SetPartitionDevice( - kPartitionNameRoot, install_plan->target_slot, target_root); - state->fake_boot_control_.SetPartitionDevice( - kPartitionNameKernel, install_plan->target_slot, target_kernel); + kPartitionNameRoot, install_plan->target_slot, state->result_img->path()); + state->fake_boot_control_.SetPartitionDevice(kPartitionNameKernel, + install_plan->target_slot, + state->result_kernel->path()); ErrorCode expected_error, actual_error; bool continue_writing; @@ -885,21 +912,16 @@ void VerifyPayloadResult(DeltaPerformer* performer, return; } - brillo::Blob updated_kernel_partition; - if (minor_version == kSourceMinorPayloadVersion) { - CompareFilesByBlock( - state->result_kernel, state->new_kernel, state->kernel_size); - CompareFilesByBlock(state->result_img, state->b_img, state->image_size); - EXPECT_TRUE( - utils::ReadFile(state->result_kernel, &updated_kernel_partition)); - } else { - CompareFilesByBlock( - state->old_kernel, state->new_kernel, state->kernel_size); - CompareFilesByBlock(state->a_img, state->b_img, state->image_size); - EXPECT_TRUE(utils::ReadFile(state->old_kernel, &updated_kernel_partition)); - } + CompareFilesByBlock(state->result_kernel->path(), + state->new_kernel->path(), + state->kernel_size); + CompareFilesByBlock( + state->result_img->path(), state->b_img->path(), state->image_size); - ASSERT_GE(updated_kernel_partition.size(), arraysize(kNewData)); + brillo::Blob updated_kernel_partition; + EXPECT_TRUE( + utils::ReadFile(state->result_kernel->path(), &updated_kernel_partition)); + ASSERT_GE(updated_kernel_partition.size(), base::size(kNewData)); EXPECT_TRUE(std::equal(std::begin(kNewData), std::end(kNewData), updated_kernel_partition.begin())); @@ -917,9 +939,10 @@ void VerifyPayloadResult(DeltaPerformer* performer, EXPECT_EQ(state->image_size, partitions[0].target_size); brillo::Blob expected_new_rootfs_hash; - EXPECT_EQ(static_cast<off_t>(state->image_size), - HashCalculator::RawHashOfFile( - state->b_img, state->image_size, &expected_new_rootfs_hash)); + EXPECT_EQ( + static_cast<off_t>(state->image_size), + HashCalculator::RawHashOfFile( + state->b_img->path(), state->image_size, &expected_new_rootfs_hash)); EXPECT_EQ(expected_new_rootfs_hash, partitions[0].target_hash); } @@ -944,7 +967,6 @@ void VerifyPayload(DeltaPerformer* performer, void DoSmallImageTest(bool full_kernel, bool full_rootfs, - bool noop, ssize_t chunk_size, SignatureTest signature_test, bool hash_checks_mandatory, @@ -953,22 +975,13 @@ void DoSmallImageTest(bool full_kernel, DeltaPerformer* performer = nullptr; GenerateDeltaFile(full_kernel, full_rootfs, - noop, chunk_size, signature_test, &state, minor_version); - ScopedPathUnlinker a_img_unlinker(state.a_img); - ScopedPathUnlinker b_img_unlinker(state.b_img); - ScopedPathUnlinker new_img_unlinker(state.result_img); - ScopedPathUnlinker delta_unlinker(state.delta_path); - ScopedPathUnlinker old_kernel_unlinker(state.old_kernel); - ScopedPathUnlinker new_kernel_unlinker(state.new_kernel); - ScopedPathUnlinker result_kernel_unlinker(state.result_kernel); ApplyDeltaFile(full_kernel, full_rootfs, - noop, signature_test, &state, hash_checks_mandatory, @@ -983,17 +996,10 @@ void DoOperationHashMismatchTest(OperationHashTest op_hash_test, bool hash_checks_mandatory) { DeltaState state; uint64_t minor_version = kFullPayloadMinorVersion; - GenerateDeltaFile( - true, true, false, -1, kSignatureGenerated, &state, minor_version); - ScopedPathUnlinker a_img_unlinker(state.a_img); - ScopedPathUnlinker b_img_unlinker(state.b_img); - ScopedPathUnlinker delta_unlinker(state.delta_path); - ScopedPathUnlinker old_kernel_unlinker(state.old_kernel); - ScopedPathUnlinker new_kernel_unlinker(state.new_kernel); + GenerateDeltaFile(true, true, -1, kSignatureGenerated, &state, minor_version); DeltaPerformer* performer = nullptr; ApplyDeltaFile(true, true, - false, kSignatureGenerated, &state, hash_checks_mandatory, @@ -1003,166 +1009,242 @@ void DoOperationHashMismatchTest(OperationHashTest op_hash_test, delete performer; } -TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageTest) { - DoSmallImageTest(false, - false, - false, - -1, - kSignatureGenerator, - false, - kInPlaceMinorPayloadVersion); +TEST_F(DeltaPerformerIntegrationTest, RunAsRootSmallImageTest) { + DoSmallImageTest( + false, false, -1, kSignatureGenerator, false, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootSmallImageSignaturePlaceholderTest) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignaturePlaceholderTest) { DoSmallImageTest(false, false, - false, -1, kSignatureGeneratedPlaceholder, false, - kInPlaceMinorPayloadVersion); + kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootSmallImageSignaturePlaceholderMismatchTest) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignaturePlaceholderMismatchTest) { DeltaState state; GenerateDeltaFile(false, false, - false, -1, kSignatureGeneratedPlaceholderMismatch, &state, - kInPlaceMinorPayloadVersion); + kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageChunksTest) { +TEST_F(DeltaPerformerIntegrationTest, RunAsRootSmallImageChunksTest) { DoSmallImageTest(false, false, - false, kBlockSize, kSignatureGenerator, false, - kInPlaceMinorPayloadVersion); -} - -TEST(DeltaPerformerIntegrationTest, RunAsRootFullKernelSmallImageTest) { - DoSmallImageTest(true, - false, - false, - -1, - kSignatureGenerator, - false, - kInPlaceMinorPayloadVersion); + kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootFullSmallImageTest) { - DoSmallImageTest(true, - true, - false, - -1, - kSignatureGenerator, - true, - kFullPayloadMinorVersion); +TEST_F(DeltaPerformerIntegrationTest, RunAsRootFullKernelSmallImageTest) { + DoSmallImageTest( + true, false, -1, kSignatureGenerator, false, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootNoopSmallImageTest) { - DoSmallImageTest(false, - false, - true, - -1, - kSignatureGenerator, - false, - kInPlaceMinorPayloadVersion); +TEST_F(DeltaPerformerIntegrationTest, RunAsRootFullSmallImageTest) { + DoSmallImageTest( + true, true, -1, kSignatureGenerator, true, kFullPayloadMinorVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignNoneTest) { - DoSmallImageTest(false, - false, - false, - -1, - kSignatureNone, - false, - kInPlaceMinorPayloadVersion); +TEST_F(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignNoneTest) { + DoSmallImageTest( + false, false, -1, kSignatureNone, false, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedTest) { - DoSmallImageTest(false, - false, - false, - -1, - kSignatureGenerated, - true, - kInPlaceMinorPayloadVersion); +TEST_F(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedTest) { + DoSmallImageTest( + false, false, -1, kSignatureGenerated, true, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellTest) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignGeneratedShellTest) { DoSmallImageTest(false, false, - false, -1, kSignatureGeneratedShell, false, - kInPlaceMinorPayloadVersion); + kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootSmallImageSignGeneratedShellECKeyTest) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignGeneratedShellECKeyTest) { DoSmallImageTest(false, false, - false, -1, kSignatureGeneratedShellECKey, false, - kInPlaceMinorPayloadVersion); + kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootSmallImageSignGeneratedShellBadKeyTest) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignGeneratedShellBadKeyTest) { DoSmallImageTest(false, false, - false, -1, kSignatureGeneratedShellBadKey, false, - kInPlaceMinorPayloadVersion); + kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootSmallImageSignGeneratedShellRotateCl1Test) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignGeneratedShellRotateCl1Test) { DoSmallImageTest(false, false, - false, -1, kSignatureGeneratedShellRotateCl1, false, - kInPlaceMinorPayloadVersion); + kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootSmallImageSignGeneratedShellRotateCl2Test) { +TEST_F(DeltaPerformerIntegrationTest, + RunAsRootSmallImageSignGeneratedShellRotateCl2Test) { DoSmallImageTest(false, false, - false, -1, kSignatureGeneratedShellRotateCl2, false, - kInPlaceMinorPayloadVersion); + kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSourceOpsTest) { - DoSmallImageTest(false, - false, - false, - -1, - kSignatureGenerator, - false, - kSourceMinorPayloadVersion); +TEST_F(DeltaPerformerIntegrationTest, RunAsRootSmallImageSourceOpsTest) { + DoSmallImageTest( + false, false, -1, kSignatureGenerator, false, kSourceMinorPayloadVersion); } -TEST(DeltaPerformerIntegrationTest, - RunAsRootMandatoryOperationHashMismatchTest) { +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 e9022bab..840ecf6c 100644 --- a/payload_consumer/delta_performer_unittest.cc +++ b/payload_consumer/delta_performer_unittest.cc @@ -20,6 +20,8 @@ #include <inttypes.h> #include <time.h> +#include <algorithm> +#include <map> #include <memory> #include <string> #include <vector> @@ -27,22 +29,29 @@ #include <base/files/file_path.h> #include <base/files/file_util.h> #include <base/files/scoped_temp_dir.h> +#include <base/stl_util.h> #include <base/strings/string_number_conversions.h> #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> +#include <brillo/secure_blob.h> #include <gmock/gmock.h> #include <google/protobuf/repeated_field.h> #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/hash_calculator.h" +#include "update_engine/common/mock_download_action.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" #include "update_engine/payload_consumer/fake_file_descriptor.h" -#include "update_engine/payload_consumer/mock_download_action.h" +#include "update_engine/payload_consumer/mock_partition_writer.h" #include "update_engine/payload_consumer/payload_constants.h" +#include "update_engine/payload_consumer/payload_metadata.h" #include "update_engine/payload_generator/bzip.h" #include "update_engine/payload_generator/extent_ranges.h" #include "update_engine/payload_generator/payload_file.h" @@ -55,8 +64,9 @@ using std::string; using std::vector; using test_utils::GetBuildArtifactsPath; using test_utils::kRandomString; -using test_utils::System; using testing::_; +using testing::Return; +using ::testing::Sequence; extern const char* kUnittestPrivateKeyPath; extern const char* kUnittestPublicKeyPath; @@ -198,7 +208,7 @@ class DeltaPerformerTest : public ::testing::Test { uint64_t major_version, uint32_t minor_version, PartitionConfig* old_part = nullptr) { - test_utils::ScopedTempFile blob_file("Blob-XXXXXX"); + ScopedTempFile blob_file("Blob-XXXXXX"); EXPECT_TRUE(test_utils::WriteFileVector(blob_file.path(), blob_data)); PayloadGenerationConfig config; @@ -224,15 +234,15 @@ class DeltaPerformerTest : public ::testing::Test { new_part.path = "/dev/zero"; new_part.size = 1234; - payload.AddPartition(*old_part, new_part, aops); + payload.AddPartition(*old_part, new_part, aops, {}, 0); // We include a kernel partition without operations. old_part->name = kPartitionNameKernel; new_part.name = kPartitionNameKernel; new_part.size = 0; - payload.AddPartition(*old_part, new_part, {}); + payload.AddPartition(*old_part, new_part, {}, {}, 0); - test_utils::ScopedTempFile payload_file("Payload-XXXXXX"); + ScopedTempFile payload_file("Payload-XXXXXX"); string private_key = sign_payload ? GetBuildArtifactsPath(kUnittestPrivateKeyPath) : ""; EXPECT_TRUE(payload.WritePayload(payload_file.path(), @@ -271,7 +281,14 @@ class DeltaPerformerTest : public ::testing::Test { const string& source_path, bool expect_success) { return ApplyPayloadToData( - payload_data, source_path, brillo::Blob(), expect_success); + &performer_, payload_data, source_path, brillo::Blob(), expect_success); + } + brillo::Blob ApplyPayloadToData(const brillo::Blob& payload_data, + const string& source_path, + const brillo::Blob& target_data, + bool expect_success) { + return ApplyPayloadToData( + &performer_, payload_data, source_path, target_data, expect_success); } // Apply the payload provided in |payload_data| reading from the |source_path| @@ -279,13 +296,15 @@ class DeltaPerformerTest : public ::testing::Test { // new target file are set to |target_data| before applying the payload. // Expect result of performer_.Write() to be |expect_success|. // Returns the result of the payload application. - brillo::Blob ApplyPayloadToData(const brillo::Blob& payload_data, + brillo::Blob ApplyPayloadToData(DeltaPerformer* delta_performer, + const brillo::Blob& payload_data, const string& source_path, const brillo::Blob& target_data, bool expect_success) { - test_utils::ScopedTempFile new_part("Partition-XXXXXX"); + ScopedTempFile new_part("Partition-XXXXXX"); EXPECT_TRUE(test_utils::WriteFileVector(new_part.path(), target_data)); + payload_.size = payload_data.size(); // We installed the operations only in the rootfs partition, but the // delta performer needs to access all the partitions. fake_boot_control_.SetPartitionDevice( @@ -298,7 +317,7 @@ class DeltaPerformerTest : public ::testing::Test { kPartitionNameKernel, install_plan_.source_slot, "/dev/null"); EXPECT_EQ(expect_success, - performer_.Write(payload_data.data(), payload_data.size())); + delta_performer->Write(payload_data.data(), payload_data.size())); EXPECT_EQ(0, performer_.Close()); brillo::Blob partition_data; @@ -318,14 +337,17 @@ class DeltaPerformerTest : public ::testing::Test { // Set a valid magic string and version number 1. EXPECT_TRUE(performer_.Write("CrAU", 4)); - uint64_t version = htobe64(kChromeOSMajorPayloadVersion); + uint64_t version = htobe64(kBrilloMajorPayloadVersion); EXPECT_TRUE(performer_.Write(&version, 8)); payload_.metadata_size = expected_metadata_size; + payload_.size = actual_metadata_size + 1; ErrorCode error_code; - // When filling in size in manifest, exclude the size of the 20-byte header. - uint64_t size_in_manifest = htobe64(actual_metadata_size - 20); - bool result = performer_.Write(&size_in_manifest, 8, &error_code); + // 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); + auto signature_size = htobe64(10); + bool result = performer_.Write(&signature_size, 4, &error_code); if (expected_metadata_size == actual_metadata_size || !hash_checks_mandatory) { EXPECT_TRUE(result); @@ -337,7 +359,7 @@ class DeltaPerformerTest : public ::testing::Test { EXPECT_LT(performer_.Close(), 0); } - // Generates a valid delta file but tests the delta performer by suppling + // Generates a valid delta file but tests the delta performer by supplying // different metadata signatures as per metadata_signature_test flag and // sees if the result of the parsing are as per hash_checks_mandatory flag. void DoMetadataSignatureTest(MetadataSignatureTest metadata_signature_test, @@ -347,9 +369,10 @@ class DeltaPerformerTest : public ::testing::Test { brillo::Blob payload = GeneratePayload(brillo::Blob(), vector<AnnotatedOperation>(), sign_payload, - kChromeOSMajorPayloadVersion, + kBrilloMajorPayloadVersion, kFullPayloadMinorVersion); + payload_.size = payload.size(); LOG(INFO) << "Payload size: " << payload.size(); install_plan_.hash_checks_mandatory = hash_checks_mandatory; @@ -361,6 +384,9 @@ class DeltaPerformerTest : public ::testing::Test { switch (metadata_signature_test) { case kEmptyMetadataSignature: payload_.metadata_signature.clear(); + // We need to set the signature size in a signed payload to zero. + std::fill( + std::next(payload.begin(), 20), std::next(payload.begin(), 24), 0); expected_result = MetadataParseResult::kError; expected_error = ErrorCode::kDownloadMetadataSignatureMissingError; break; @@ -406,23 +432,6 @@ class DeltaPerformerTest : public ::testing::Test { EXPECT_EQ(payload_.metadata_size, performer_.metadata_size_); } - // Helper function to pretend that the ECC file descriptor was already opened. - // Returns a pointer to the created file descriptor. - FakeFileDescriptor* SetFakeECCFile(size_t size) { - EXPECT_FALSE(performer_.source_ecc_fd_) << "source_ecc_fd_ already open."; - FakeFileDescriptor* ret = new FakeFileDescriptor(); - fake_ecc_fd_.reset(ret); - // Call open to simulate it was already opened. - ret->Open("", 0); - ret->SetFileSize(size); - performer_.source_ecc_fd_ = fake_ecc_fd_; - return ret; - } - - uint64_t GetSourceEccRecoveredFailures() const { - return performer_.source_ecc_recovered_failures_; - } - FakePrefs prefs_; InstallPlan install_plan_; InstallPlan::Payload payload_; @@ -455,7 +464,7 @@ TEST_F(DeltaPerformerTest, FullPayloadWriteTest) { brillo::Blob payload_data = GeneratePayload(expected_data, aops, false, - kChromeOSMajorPayloadVersion, + kBrilloMajorPayloadVersion, kFullPayloadMinorVersion); EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true)); @@ -477,7 +486,7 @@ TEST_F(DeltaPerformerTest, ShouldCancelTest) { brillo::Blob payload_data = GeneratePayload(expected_data, aops, false, - kChromeOSMajorPayloadVersion, + kBrilloMajorPayloadVersion, kFullPayloadMinorVersion); testing::Mock::VerifyAndClearExpectations(&mock_delegate_); @@ -579,7 +588,7 @@ TEST_F(DeltaPerformerTest, SourceCopyOperationTest) { EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash)); aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size()); - test_utils::ScopedTempFile source("Source-XXXXXX"); + ScopedTempFile source("Source-XXXXXX"); EXPECT_TRUE(test_utils::WriteFileVector(source.path(), expected_data)); PartitionConfig old_part(kPartitionNameRoot); @@ -607,7 +616,7 @@ TEST_F(DeltaPerformerTest, PuffdiffOperationTest) { EXPECT_TRUE(HashCalculator::RawHashOfData(src, &src_hash)); aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size()); - test_utils::ScopedTempFile source("Source-XXXXXX"); + ScopedTempFile source("Source-XXXXXX"); EXPECT_TRUE(test_utils::WriteFileVector(source.path(), src)); PartitionConfig old_part(kPartitionNameRoot); @@ -635,7 +644,7 @@ TEST_F(DeltaPerformerTest, SourceHashMismatchTest) { EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash)); aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size()); - test_utils::ScopedTempFile source("Source-XXXXXX"); + ScopedTempFile source("Source-XXXXXX"); EXPECT_TRUE(test_utils::WriteFileVector(source.path(), actual_data)); PartitionConfig old_part(kPartitionNameRoot); @@ -648,103 +657,14 @@ TEST_F(DeltaPerformerTest, SourceHashMismatchTest) { EXPECT_EQ(actual_data, ApplyPayload(payload_data, source.path(), false)); } -// Test that the error-corrected file descriptor is used to read the partition -// since the source partition doesn't match the operation hash. -TEST_F(DeltaPerformerTest, ErrorCorrectionSourceCopyFallbackTest) { - constexpr size_t kCopyOperationSize = 4 * 4096; - test_utils::ScopedTempFile source("Source-XXXXXX"); - // Write invalid data to the source image, which doesn't match the expected - // hash. - brillo::Blob invalid_data(kCopyOperationSize, 0x55); - EXPECT_TRUE(test_utils::WriteFileVector(source.path(), invalid_data)); - - // Setup the fec file descriptor as the fake stream, which matches - // |expected_data|. - FakeFileDescriptor* fake_fec = SetFakeECCFile(kCopyOperationSize); - brillo::Blob expected_data = FakeFileDescriptorData(kCopyOperationSize); - - 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()); - EXPECT_EQ(1U, GetSourceEccRecoveredFailures()); -} - -// Test that the error-corrected file descriptor is used to read a partition -// when no hash is available for SOURCE_COPY but it falls back to the normal -// file descriptor when the size of the error corrected one is too small. -TEST_F(DeltaPerformerTest, ErrorCorrectionSourceCopyWhenNoHashFallbackTest) { - constexpr size_t kCopyOperationSize = 4 * 4096; - test_utils::ScopedTempFile source("Source-XXXXXX"); - // Setup the source path with the right expected data. - brillo::Blob expected_data = FakeFileDescriptorData(kCopyOperationSize); - EXPECT_TRUE(test_utils::WriteFileVector(source.path(), expected_data)); - - // Setup the fec file descriptor as the fake stream, with smaller data than - // 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, &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 - // reached the EOF. - EXPECT_LE(1U, fake_fec->GetReadOps().size()); - // This fallback doesn't count as an error-corrected operation since the - // operation hash was not available. - EXPECT_EQ(0U, GetSourceEccRecoveredFailures()); -} - -TEST_F(DeltaPerformerTest, ChooseSourceFDTest) { - constexpr size_t kSourceSize = 4 * 4096; - test_utils::ScopedTempFile source("Source-XXXXXX"); - // Write invalid data to the source image, which doesn't match the expected - // hash. - brillo::Blob invalid_data(kSourceSize, 0x55); - EXPECT_TRUE(test_utils::WriteFileVector(source.path(), invalid_data)); - - performer_.source_fd_ = std::make_shared<EintrSafeFileDescriptor>(); - performer_.source_fd_->Open(source.path().c_str(), O_RDONLY); - performer_.block_size_ = 4096; - - // Setup the fec file descriptor as the fake stream, which matches - // |expected_data|. - FakeFileDescriptor* fake_fec = SetFakeECCFile(kSourceSize); - brillo::Blob expected_data = FakeFileDescriptorData(kSourceSize); - - InstallOperation op; - *(op.add_src_extents()) = ExtentForRange(0, kSourceSize / 4096); - brillo::Blob src_hash; - EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash)); - op.set_src_sha256_hash(src_hash.data(), src_hash.size()); - - ErrorCode error = ErrorCode::kSuccess; - EXPECT_EQ(performer_.source_ecc_fd_, performer_.ChooseSourceFD(op, &error)); - EXPECT_EQ(ErrorCode::kSuccess, error); - // Verify that the fake_fec was actually used. - EXPECT_EQ(1U, fake_fec->GetReadOps().size()); - EXPECT_EQ(1U, GetSourceEccRecoveredFailures()); -} - TEST_F(DeltaPerformerTest, ExtentsToByteStringTest) { uint64_t test[] = {1, 1, 4, 2, 0, 1}; - static_assert(arraysize(test) % 2 == 0, "Array size uneven"); + static_assert(base::size(test) % 2 == 0, "Array size uneven"); const uint64_t block_size = 4096; const uint64_t file_length = 4 * block_size - 13; google::protobuf::RepeatedPtrField<Extent> extents; - for (size_t i = 0; i < arraysize(test); i += 2) { + for (size_t i = 0; i < base::size(test); i += 2) { *(extents.Add()) = ExtentForRange(test[i], test[i + 1]); } @@ -758,27 +678,32 @@ TEST_F(DeltaPerformerTest, ExtentsToByteStringTest) { TEST_F(DeltaPerformerTest, ValidateManifestFullGoodTest) { // The Manifest we are validating. DeltaArchiveManifest manifest; - manifest.mutable_new_kernel_info(); - manifest.mutable_new_rootfs_info(); + for (const auto& part_name : {"kernel", "rootfs"}) { + auto part = manifest.add_partitions(); + part->set_partition_name(part_name); + part->mutable_new_partition_info(); + } manifest.set_minor_version(kFullPayloadMinorVersion); RunManifestValidation(manifest, - kChromeOSMajorPayloadVersion, + kBrilloMajorPayloadVersion, InstallPayloadType::kFull, ErrorCode::kSuccess); } -TEST_F(DeltaPerformerTest, ValidateManifestDeltaGoodTest) { +TEST_F(DeltaPerformerTest, ValidateManifestDeltaMaxGoodTest) { // The Manifest we are validating. DeltaArchiveManifest manifest; - manifest.mutable_old_kernel_info(); - manifest.mutable_old_rootfs_info(); - manifest.mutable_new_kernel_info(); - manifest.mutable_new_rootfs_info(); + for (const auto& part_name : {"kernel", "rootfs"}) { + auto part = manifest.add_partitions(); + part->set_partition_name(part_name); + part->mutable_old_partition_info(); + part->mutable_new_partition_info(); + } manifest.set_minor_version(kMaxSupportedMinorPayloadVersion); RunManifestValidation(manifest, - kChromeOSMajorPayloadVersion, + kBrilloMajorPayloadVersion, InstallPayloadType::kDelta, ErrorCode::kSuccess); } @@ -786,14 +711,16 @@ TEST_F(DeltaPerformerTest, ValidateManifestDeltaGoodTest) { TEST_F(DeltaPerformerTest, ValidateManifestDeltaMinGoodTest) { // The Manifest we are validating. DeltaArchiveManifest manifest; - manifest.mutable_old_kernel_info(); - manifest.mutable_old_rootfs_info(); - manifest.mutable_new_kernel_info(); - manifest.mutable_new_rootfs_info(); + for (const auto& part_name : {"kernel", "rootfs"}) { + auto part = manifest.add_partitions(); + part->set_partition_name(part_name); + part->mutable_old_partition_info(); + part->mutable_new_partition_info(); + } manifest.set_minor_version(kMinSupportedMinorPayloadVersion); RunManifestValidation(manifest, - kChromeOSMajorPayloadVersion, + kBrilloMajorPayloadVersion, InstallPayloadType::kDelta, ErrorCode::kSuccess); } @@ -811,9 +738,11 @@ TEST_F(DeltaPerformerTest, ValidateManifestFullUnsetMinorVersion) { TEST_F(DeltaPerformerTest, ValidateManifestDeltaUnsetMinorVersion) { // The Manifest we are validating. DeltaArchiveManifest manifest; - // Add an empty old_rootfs_info() to trick the DeltaPerformer into think that - // this is a delta payload manifest with a missing minor version. - manifest.mutable_old_rootfs_info(); + // Add an empty rootfs partition info to trick the DeltaPerformer into think + // that this is a delta payload manifest with a missing minor version. + auto rootfs = manifest.add_partitions(); + rootfs->set_partition_name("rootfs"); + rootfs->mutable_old_partition_info(); RunManifestValidation(manifest, kMaxSupportedMajorPayloadVersion, @@ -824,27 +753,15 @@ TEST_F(DeltaPerformerTest, ValidateManifestDeltaUnsetMinorVersion) { TEST_F(DeltaPerformerTest, ValidateManifestFullOldKernelTest) { // The Manifest we are validating. DeltaArchiveManifest manifest; - manifest.mutable_old_kernel_info(); - manifest.mutable_new_kernel_info(); - manifest.mutable_new_rootfs_info(); - manifest.set_minor_version(kMaxSupportedMinorPayloadVersion); - - RunManifestValidation(manifest, - kChromeOSMajorPayloadVersion, - InstallPayloadType::kFull, - ErrorCode::kPayloadMismatchedType); -} - -TEST_F(DeltaPerformerTest, ValidateManifestFullOldRootfsTest) { - // The Manifest we are validating. - DeltaArchiveManifest manifest; - manifest.mutable_old_rootfs_info(); - manifest.mutable_new_kernel_info(); - manifest.mutable_new_rootfs_info(); - manifest.set_minor_version(kMaxSupportedMinorPayloadVersion); - + for (const auto& part_name : {"kernel", "rootfs"}) { + auto part = manifest.add_partitions(); + part->set_partition_name(part_name); + part->mutable_old_partition_info(); + part->mutable_new_partition_info(); + } + manifest.mutable_partitions(0)->clear_old_partition_info(); RunManifestValidation(manifest, - kChromeOSMajorPayloadVersion, + kBrilloMajorPayloadVersion, InstallPayloadType::kFull, ErrorCode::kPayloadMismatchedType); } @@ -869,8 +786,8 @@ TEST_F(DeltaPerformerTest, ValidateManifestBadMinorVersion) { // Generate a bad version number. manifest.set_minor_version(kMaxSupportedMinorPayloadVersion + 10000); - // Mark the manifest as a delta payload by setting old_rootfs_info. - manifest.mutable_old_rootfs_info(); + // Mark the manifest as a delta payload by setting |old_partition_info|. + manifest.add_partitions()->mutable_old_partition_info(); RunManifestValidation(manifest, kMaxSupportedMajorPayloadVersion, @@ -892,20 +809,50 @@ 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))); uint64_t major_version = htobe64(kBrilloMajorPayloadVersion); - EXPECT_TRUE(performer_.Write(&major_version, 8)); + EXPECT_TRUE( + performer_.Write(&major_version, PayloadMetadata::kDeltaVersionSize)); uint64_t manifest_size = rand_r(&seed) % 256; + uint32_t metadata_signature_size = rand_r(&seed) % 256; + + // The payload size has to be bigger than the |metadata_size| and + // |metadata_signature_size| + payload_.size = PayloadMetadata::kDeltaManifestSizeOffset + + PayloadMetadata::kDeltaManifestSizeSize + + PayloadMetadata::kDeltaMetadataSignatureSizeSize + + manifest_size + metadata_signature_size + 1; + uint64_t manifest_size_be = htobe64(manifest_size); - EXPECT_TRUE(performer_.Write(&manifest_size_be, 8)); + EXPECT_TRUE(performer_.Write(&manifest_size_be, + PayloadMetadata::kDeltaManifestSizeSize)); - uint32_t metadata_signature_size = rand_r(&seed) % 256; uint32_t metadata_signature_size_be = htobe32(metadata_signature_size); - EXPECT_TRUE(performer_.Write(&metadata_signature_size_be, 4)); + EXPECT_TRUE( + performer_.Write(&metadata_signature_size_be, + PayloadMetadata::kDeltaMetadataSignatureSizeSize)); EXPECT_LT(performer_.Close(), 0); @@ -915,10 +862,74 @@ TEST_F(DeltaPerformerTest, BrilloMetadataSignatureSizeTest) { EXPECT_EQ(metadata_signature_size, performer_.metadata_signature_size_); } +TEST_F(DeltaPerformerTest, BrilloMetadataSizeNOKTest) { + unsigned int seed = time(nullptr); + EXPECT_TRUE(performer_.Write(kDeltaMagic, sizeof(kDeltaMagic))); + + uint64_t major_version = htobe64(kBrilloMajorPayloadVersion); + EXPECT_TRUE( + performer_.Write(&major_version, PayloadMetadata::kDeltaVersionSize)); + + uint64_t manifest_size = UINT64_MAX - 600; // Subtract to avoid wrap around. + uint64_t manifest_offset = PayloadMetadata::kDeltaManifestSizeOffset + + PayloadMetadata::kDeltaManifestSizeSize + + PayloadMetadata::kDeltaMetadataSignatureSizeSize; + payload_.metadata_size = manifest_offset + manifest_size; + uint32_t metadata_signature_size = rand_r(&seed) % 256; + + // The payload size is greater than the payload header but smaller than + // |metadata_signature_size| + |metadata_size| + payload_.size = manifest_offset + metadata_signature_size + 1; + + uint64_t manifest_size_be = htobe64(manifest_size); + EXPECT_TRUE(performer_.Write(&manifest_size_be, + PayloadMetadata::kDeltaManifestSizeSize)); + uint32_t metadata_signature_size_be = htobe32(metadata_signature_size); + + ErrorCode error; + EXPECT_FALSE( + performer_.Write(&metadata_signature_size_be, + PayloadMetadata::kDeltaMetadataSignatureSizeSize + 1, + &error)); + + EXPECT_EQ(ErrorCode::kDownloadInvalidMetadataSize, error); +} + +TEST_F(DeltaPerformerTest, BrilloMetadataSignatureSizeNOKTest) { + unsigned int seed = time(nullptr); + EXPECT_TRUE(performer_.Write(kDeltaMagic, sizeof(kDeltaMagic))); + + uint64_t major_version = htobe64(kBrilloMajorPayloadVersion); + EXPECT_TRUE( + performer_.Write(&major_version, PayloadMetadata::kDeltaVersionSize)); + + uint64_t manifest_size = rand_r(&seed) % 256; + // Subtract from UINT32_MAX to avoid wrap around. + uint32_t metadata_signature_size = UINT32_MAX - 600; + + // The payload size is greater than |manifest_size| but smaller than + // |metadata_signature_size| + payload_.size = manifest_size + 1; + + uint64_t manifest_size_be = htobe64(manifest_size); + EXPECT_TRUE(performer_.Write(&manifest_size_be, + PayloadMetadata::kDeltaManifestSizeSize)); + + uint32_t metadata_signature_size_be = htobe32(metadata_signature_size); + ErrorCode error; + EXPECT_FALSE( + performer_.Write(&metadata_signature_size_be, + PayloadMetadata::kDeltaMetadataSignatureSizeSize + 1, + &error)); + + EXPECT_EQ(ErrorCode::kDownloadInvalidMetadataSize, error); +} + TEST_F(DeltaPerformerTest, BrilloParsePayloadMetadataTest) { brillo::Blob payload_data = GeneratePayload( {}, {}, true, kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion); install_plan_.hash_checks_mandatory = true; + payload_.size = payload_data.size(); ErrorCode error; EXPECT_EQ(MetadataParseResult::kSuccess, performer_.ParsePayloadMetadata(payload_data, &error)); @@ -1073,4 +1084,117 @@ TEST_F(DeltaPerformerTest, ConfVersionsMatch) { EXPECT_EQ(kMaxSupportedMajorPayloadVersion, major_version); } +TEST_F(DeltaPerformerTest, FullPayloadCanResumeTest) { + payload_.type = InstallPayloadType::kFull; + brillo::Blob expected_data = + brillo::Blob(std::begin(kRandomString), std::end(kRandomString)); + expected_data.resize(4096); // block size + vector<AnnotatedOperation> aops; + AnnotatedOperation aop; + *(aop.op.add_dst_extents()) = ExtentForRange(0, 1); + aop.op.set_data_offset(0); + aop.op.set_data_length(expected_data.size()); + aop.op.set_type(InstallOperation::REPLACE); + aops.push_back(aop); + + brillo::Blob payload_data = GeneratePayload(expected_data, + aops, + false, + kBrilloMajorPayloadVersion, + kFullPayloadMinorVersion); + + ASSERT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true)); + performer_.CheckpointUpdateProgress(true); + const std::string payload_id = "12345"; + prefs_.SetString(kPrefsUpdateCheckResponseHash, payload_id); + ASSERT_TRUE(DeltaPerformer::CanResumeUpdate(&prefs_, payload_id)); +} + +class TestDeltaPerformer : public DeltaPerformer { + public: + using DeltaPerformer::DeltaPerformer; + + std::unique_ptr<PartitionWriter> CreatePartitionWriter( + const PartitionUpdate& partition_update, + const InstallPlan::Partition& install_part, + DynamicPartitionControlInterface* dynamic_control, + size_t block_size, + bool is_interactive, + bool is_dynamic_partition) { + LOG(INFO) << __FUNCTION__ << ": " << install_part.name; + auto node = partition_writers_.extract(install_part.name); + return std::move(node.mapped()); + } + + bool ShouldCheckpoint() override { return true; } + + std::map<std::string, std::unique_ptr<MockPartitionWriter>> + partition_writers_; +}; + +namespace { +AnnotatedOperation GetSourceCopyOp(uint32_t src_block, + uint32_t dst_block, + const void* data, + size_t length) { + AnnotatedOperation aop; + *(aop.op.add_src_extents()) = ExtentForRange(0, 1); + *(aop.op.add_dst_extents()) = ExtentForRange(0, 1); + aop.op.set_type(InstallOperation::SOURCE_COPY); + brillo::Blob src_hash; + HashCalculator::RawHashOfBytes(data, length, &src_hash); + aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size()); + return aop; +} +} // namespace + +TEST_F(DeltaPerformerTest, SetNextOpIndex) { + TestDeltaPerformer delta_performer{&prefs_, + &fake_boot_control_, + &fake_hardware_, + &mock_delegate_, + &install_plan_, + &payload_, + false}; + brillo::Blob expected_data(std::begin(kRandomString), + std::end(kRandomString)); + expected_data.resize(4096 * 2); // block size + AnnotatedOperation aop; + + 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(); + + delta_performer.partition_writers_[kPartitionNameRoot] = + std::make_unique<MockPartitionWriter>(); + auto& writer1 = *delta_performer.partition_writers_[kPartitionNameRoot]; + + Sequence seq; + std::vector<size_t> indices; + EXPECT_CALL(writer1, CheckpointUpdateProgress(_)) + .WillRepeatedly( + [&indices](size_t index) mutable { indices.emplace_back(index); }); + EXPECT_CALL(writer1, Init(_, true, _)).Times(1).WillOnce(Return(true)); + EXPECT_CALL(writer1, PerformSourceCopyOperation(_, _)) + .Times(2) + .WillRepeatedly(Return(true)); + + brillo::Blob payload_data = GeneratePayload( + brillo::Blob(), + {GetSourceCopyOp(0, 0, expected_data.data(), 4096), + GetSourceCopyOp(1, 1, expected_data.data() + 4096, 4096)}, + false, + &old_part); + + ApplyPayloadToData(&delta_performer, payload_data, source.path(), {}, true); + ASSERT_TRUE(std::is_sorted(indices.begin(), indices.end())); + ASSERT_GT(indices.size(), 0UL); + + // Should be equal to number of operations + ASSERT_EQ(indices[indices.size() - 1], 2UL); +} + } // namespace chromeos_update_engine diff --git a/payload_consumer/extent_reader.cc b/payload_consumer/extent_reader.cc index ad983ae1..3c7329dc 100644 --- a/payload_consumer/extent_reader.cc +++ b/payload_consumer/extent_reader.cc @@ -77,7 +77,7 @@ bool DirectExtentReader::Read(void* buffer, size_t count) { std::min(count - bytes_read, cur_extent_bytes_left); ssize_t out_bytes_read; - TEST_AND_RETURN_FALSE(utils::PReadAll( + TEST_AND_RETURN_FALSE(utils::ReadAll( fd_, bytes + bytes_read, bytes_to_read, diff --git a/payload_consumer/extent_reader_unittest.cc b/payload_consumer/extent_reader_unittest.cc index b7059bc9..686f14d8 100644 --- a/payload_consumer/extent_reader_unittest.cc +++ b/payload_consumer/extent_reader_unittest.cc @@ -72,7 +72,7 @@ class ExtentReaderTest : public ::testing::Test { } FileDescriptorPtr fd_; - test_utils::ScopedTempFile temp_file_{"ExtentReaderTest-file.XXXXXX"}; + ScopedTempFile temp_file_{"ExtentReaderTest-file.XXXXXX"}; brillo::Blob sample_; }; diff --git a/payload_consumer/extent_writer.h b/payload_consumer/extent_writer.h index 9e53561e..8b1b5326 100644 --- a/payload_consumer/extent_writer.h +++ b/payload_consumer/extent_writer.h @@ -38,8 +38,7 @@ class ExtentWriter { virtual ~ExtentWriter() = default; // Returns true on success. - virtual bool Init(FileDescriptorPtr fd, - const google::protobuf::RepeatedPtrField<Extent>& extents, + virtual bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents, uint32_t block_size) = 0; // Returns true on success. @@ -51,13 +50,11 @@ class ExtentWriter { class DirectExtentWriter : public ExtentWriter { public: - DirectExtentWriter() = default; + explicit DirectExtentWriter(FileDescriptorPtr fd) : fd_(fd) {} ~DirectExtentWriter() override = default; - bool Init(FileDescriptorPtr fd, - const google::protobuf::RepeatedPtrField<Extent>& extents, + bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents, uint32_t block_size) override { - fd_ = fd; block_size_ = block_size; extents_ = extents; cur_extent_ = extents_.begin(); diff --git a/payload_consumer/extent_writer_unittest.cc b/payload_consumer/extent_writer_unittest.cc index aef856bf..5c67d3e8 100644 --- a/payload_consumer/extent_writer_unittest.cc +++ b/payload_consumer/extent_writer_unittest.cc @@ -59,15 +59,14 @@ class ExtentWriterTest : public ::testing::Test { void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size); FileDescriptorPtr fd_; - test_utils::ScopedTempFile temp_file_{"ExtentWriterTest-file.XXXXXX"}; + ScopedTempFile temp_file_{"ExtentWriterTest-file.XXXXXX"}; }; TEST_F(ExtentWriterTest, SimpleTest) { vector<Extent> extents = {ExtentForRange(1, 1)}; const string bytes = "1234"; - DirectExtentWriter direct_writer; - EXPECT_TRUE( - direct_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize)); + DirectExtentWriter direct_writer{fd_}; + EXPECT_TRUE(direct_writer.Init({extents.begin(), extents.end()}, kBlockSize)); EXPECT_TRUE(direct_writer.Write(bytes.data(), bytes.size())); EXPECT_EQ(static_cast<off_t>(kBlockSize + bytes.size()), @@ -84,9 +83,8 @@ TEST_F(ExtentWriterTest, SimpleTest) { TEST_F(ExtentWriterTest, ZeroLengthTest) { vector<Extent> extents = {ExtentForRange(1, 1)}; - DirectExtentWriter direct_writer; - EXPECT_TRUE( - direct_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize)); + DirectExtentWriter direct_writer{fd_}; + EXPECT_TRUE(direct_writer.Init({extents.begin(), extents.end()}, kBlockSize)); EXPECT_TRUE(direct_writer.Write(nullptr, 0)); } @@ -109,9 +107,8 @@ void ExtentWriterTest::WriteAlignedExtents(size_t chunk_size, brillo::Blob data(kBlockSize * 3); test_utils::FillWithData(&data); - DirectExtentWriter direct_writer; - EXPECT_TRUE( - direct_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize)); + DirectExtentWriter direct_writer{fd_}; + EXPECT_TRUE(direct_writer.Init({extents.begin(), extents.end()}, kBlockSize)); size_t bytes_written = 0; while (bytes_written < data.size()) { @@ -150,9 +147,8 @@ TEST_F(ExtentWriterTest, SparseFileTest) { brillo::Blob data(17); test_utils::FillWithData(&data); - DirectExtentWriter direct_writer; - EXPECT_TRUE( - direct_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize)); + DirectExtentWriter direct_writer{fd_}; + EXPECT_TRUE(direct_writer.Init({extents.begin(), extents.end()}, kBlockSize)); size_t bytes_written = 0; while (bytes_written < (block_count * kBlockSize)) { diff --git a/payload_consumer/fake_extent_writer.h b/payload_consumer/fake_extent_writer.h index 7b2b7ac3..680b1b3b 100644 --- a/payload_consumer/fake_extent_writer.h +++ b/payload_consumer/fake_extent_writer.h @@ -33,8 +33,7 @@ class FakeExtentWriter : public ExtentWriter { ~FakeExtentWriter() override = default; // ExtentWriter overrides. - bool Init(FileDescriptorPtr /* fd */, - const google::protobuf::RepeatedPtrField<Extent>& /* extents */, + bool Init(const google::protobuf::RepeatedPtrField<Extent>& /* extents */, uint32_t /* block_size */) override { init_called_ = true; return true; diff --git a/payload_consumer/fec_file_descriptor.cc b/payload_consumer/fec_file_descriptor.cc index de22cf3f..3fee1963 100644 --- a/payload_consumer/fec_file_descriptor.cc +++ b/payload_consumer/fec_file_descriptor.cc @@ -16,6 +16,8 @@ #include "update_engine/payload_consumer/fec_file_descriptor.h" +#include <base/logging.h> + namespace chromeos_update_engine { bool FecFileDescriptor::Open(const char* path, int flags) { diff --git a/payload_consumer/file_descriptor.cc b/payload_consumer/file_descriptor.cc index 1de615cb..da76327c 100644 --- a/payload_consumer/file_descriptor.cc +++ b/payload_consumer/file_descriptor.cc @@ -21,6 +21,7 @@ #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/types.h> +#include <unistd.h> #include <base/posix/eintr_wrapper.h> @@ -28,6 +29,12 @@ namespace chromeos_update_engine { +EintrSafeFileDescriptor::~EintrSafeFileDescriptor() { + if (IsOpen()) { + Close(); + } +} + bool EintrSafeFileDescriptor::Open(const char* path, int flags, mode_t mode) { CHECK_EQ(fd_, -1); return ((fd_ = HANDLE_EINTR(open(path, flags, mode))) >= 0); @@ -125,11 +132,19 @@ bool EintrSafeFileDescriptor::BlkIoctl(int request, bool EintrSafeFileDescriptor::Flush() { CHECK_GE(fd_, 0); + // Implemented as a No-Op, as delta_performer typically uses |O_DSYNC|, except + // in interactive settings. + fsync(fd_); return true; } bool EintrSafeFileDescriptor::Close() { - CHECK_GE(fd_, 0); + if (fd_ < 0) { + return false; + } + // https://stackoverflow.com/questions/705454/does-linux-guarantee-the-contents-of-a-file-is-flushed-to-disc-after-close + // |close()| doesn't imply |fsync()|, we need to do it manually. + fsync(fd_); if (IGNORE_EINTR(close(fd_))) return false; fd_ = -1; diff --git a/payload_consumer/file_descriptor.h b/payload_consumer/file_descriptor.h index 55f76c66..faebcc1f 100644 --- a/payload_consumer/file_descriptor.h +++ b/payload_consumer/file_descriptor.h @@ -21,7 +21,7 @@ #include <sys/types.h> #include <memory> -#include <base/logging.h> +#include <base/macros.h> // Abstraction for managing opening, reading, writing and closing of file // descriptors. This includes an abstract class and one standard implementation @@ -111,6 +111,7 @@ class FileDescriptor { class EintrSafeFileDescriptor : public FileDescriptor { public: EintrSafeFileDescriptor() : fd_(-1) {} + ~EintrSafeFileDescriptor(); // Interface methods. bool Open(const char* path, int flags, mode_t mode) override; diff --git a/payload_consumer/file_descriptor_utils.cc b/payload_consumer/file_descriptor_utils.cc index 846cbd72..9a6a601e 100644 --- a/payload_consumer/file_descriptor_utils.cc +++ b/payload_consumer/file_descriptor_utils.cc @@ -82,8 +82,8 @@ bool CopyAndHashExtents(FileDescriptorPtr source, const RepeatedPtrField<Extent>& tgt_extents, uint64_t block_size, brillo::Blob* hash_out) { - DirectExtentWriter writer; - TEST_AND_RETURN_FALSE(writer.Init(target, tgt_extents, block_size)); + DirectExtentWriter writer{target}; + TEST_AND_RETURN_FALSE(writer.Init(tgt_extents, block_size)); TEST_AND_RETURN_FALSE(utils::BlocksInExtents(src_extents) == utils::BlocksInExtents(tgt_extents)); TEST_AND_RETURN_FALSE( diff --git a/payload_consumer/file_descriptor_utils_unittest.cc b/payload_consumer/file_descriptor_utils_unittest.cc index 48e610f1..478893d7 100644 --- a/payload_consumer/file_descriptor_utils_unittest.cc +++ b/payload_consumer/file_descriptor_utils_unittest.cc @@ -52,14 +52,13 @@ RepeatedPtrField<Extent> CreateExtentList( class FileDescriptorUtilsTest : public ::testing::Test { protected: void SetUp() override { - EXPECT_TRUE(utils::MakeTempFile("fd_tgt.XXXXXX", &tgt_path_, nullptr)); - EXPECT_TRUE(target_->Open(tgt_path_.c_str(), O_RDWR)); + EXPECT_TRUE(target_->Open(tgt_file_.path().c_str(), O_RDWR)); } // Check that the |target_| file contains |expected_contents|. void ExpectTarget(const std::string& expected_contents) { std::string target_contents; - EXPECT_TRUE(utils::ReadFile(tgt_path_, &target_contents)); + EXPECT_TRUE(utils::ReadFile(tgt_file_.path(), &target_contents)); EXPECT_EQ(expected_contents.size(), target_contents.size()); if (target_contents != expected_contents) { ADD_FAILURE() << "Contents don't match."; @@ -70,8 +69,7 @@ class FileDescriptorUtilsTest : public ::testing::Test { } } - // Path to the target temporary file. - std::string tgt_path_; + ScopedTempFile tgt_file_{"fd_tgt.XXXXXX"}; // Source and target file descriptor used for testing the tools. FakeFileDescriptor* fake_source_{new FakeFileDescriptor()}; diff --git a/payload_consumer/file_writer_unittest.cc b/payload_consumer/file_writer_unittest.cc index 59cfe2b9..3b959f30 100644 --- a/payload_consumer/file_writer_unittest.cc +++ b/payload_consumer/file_writer_unittest.cc @@ -35,8 +35,7 @@ namespace chromeos_update_engine { class FileWriterTest : public ::testing::Test {}; TEST(FileWriterTest, SimpleTest) { - // Create a uniquely named file for testing. - test_utils::ScopedTempFile file("FileWriterTest-XXXXXX"); + ScopedTempFile file("FileWriterTest-XXXXXX"); DirectFileWriter file_writer; EXPECT_EQ(0, file_writer.Open(file.path().c_str(), @@ -60,7 +59,7 @@ TEST(FileWriterTest, ErrorTest) { TEST(FileWriterTest, WriteErrorTest) { // Create a uniquely named file for testing. - test_utils::ScopedTempFile file("FileWriterTest-XXXXXX"); + ScopedTempFile file("FileWriterTest-XXXXXX"); DirectFileWriter file_writer; EXPECT_EQ(0, file_writer.Open(file.path().c_str(), diff --git a/payload_consumer/filesystem_verifier_action.cc b/payload_consumer/filesystem_verifier_action.cc index 36e5a35b..8b39f6d0 100644 --- a/payload_consumer/filesystem_verifier_action.cc +++ b/payload_consumer/filesystem_verifier_action.cc @@ -20,24 +20,65 @@ #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> +#include <unistd.h> #include <algorithm> #include <cstdlib> +#include <memory> #include <string> +#include <utility> #include <base/bind.h> +#include <base/strings/string_util.h> #include <brillo/data_encoding.h> +#include <brillo/message_loops/message_loop.h> +#include <brillo/secure_blob.h> #include <brillo/streams/file_stream.h> +#include "common/error_code.h" +#include "payload_generator/delta_diff_generator.h" #include "update_engine/common/utils.h" +#include "update_engine/payload_consumer/file_descriptor.h" using brillo::data_encoding::Base64Encode; using std::string; +// On a partition with verity enabled, we expect to see the following format: +// =================================================== +// Normal Filesystem Data +// (this should take most of the space, like over 90%) +// =================================================== +// Hash tree +// ~0.8% (e.g. 16M for 2GB image) +// =================================================== +// FEC data +// ~0.8% +// =================================================== +// Footer +// 4K +// =================================================== + +// For OTA that doesn't do on device verity computation, hash tree and fec data +// are written during DownloadAction as a regular InstallOp, so no special +// handling needed, we can just read the entire partition in 1 go. + +// Verity enabled case: Only Normal FS data is written during download action. +// When hasing the entire partition, we will need to build the hash tree, write +// it to disk, then build FEC, and write it to disk. Therefore, it is important +// that we finish writing hash tree before we attempt to read & hash it. The +// same principal applies to FEC data. + +// |verity_writer_| handles building and +// writing of FEC/HashTree, we just need to be careful when reading. +// Specifically, we must stop at beginning of Hash tree, let |verity_writer_| +// write both hash tree and FEC, then continue reading the remaining part of +// partition. + namespace chromeos_update_engine { namespace { const off_t kReadFileBufferSize = 128 * 1024; +constexpr float kVerityProgressPercent = 0.6; } // namespace void FilesystemVerifierAction::PerformAction() { @@ -57,7 +98,7 @@ void FilesystemVerifierAction::PerformAction() { abort_action_completer.set_code(ErrorCode::kSuccess); return; } - + install_plan_.Dump(); StartPartitionHashing(); abort_action_completer.set_should_complete(false); } @@ -68,162 +109,320 @@ void FilesystemVerifierAction::TerminateProcessing() { } void FilesystemVerifierAction::Cleanup(ErrorCode code) { - src_stream_.reset(); + partition_fd_.reset(); // This memory is not used anymore. buffer_.clear(); + // If we didn't write verity, partitions were maped. Releaase resource now. + if (!install_plan_.write_verity && + dynamic_control_->UpdateUsesSnapshotCompression()) { + LOG(INFO) << "Not writing verity and VABC is enabled, unmapping all " + "partitions"; + dynamic_control_->UnmapAllPartitions(); + } + if (cancelled_) return; if (code == ErrorCode::kSuccess && HasOutputPipe()) SetOutputObject(install_plan_); + UpdateProgress(1.0); processor_->ActionComplete(this, code); } -void FilesystemVerifierAction::StartPartitionHashing() { - if (partition_index_ == install_plan_.partitions.size()) { - Cleanup(ErrorCode::kSuccess); - return; +void FilesystemVerifierAction::UpdateProgress(double progress) { + if (delegate_ != nullptr) { + delegate_->OnVerifyProgressUpdate(progress); } +} + +void FilesystemVerifierAction::UpdatePartitionProgress(double progress) { + // We don't consider sizes of each partition. Every partition + // has the same length on progress bar. + // TODO(b/186087589): Take sizes of each partition into account. + UpdateProgress((progress + partition_index_) / + install_plan_.partitions.size()); +} + +bool FilesystemVerifierAction::InitializeFdVABC(bool should_write_verity) { const InstallPlan::Partition& partition = install_plan_.partitions[partition_index_]; - string part_path; - switch (verifier_step_) { - case VerifierStep::kVerifySourceHash: - part_path = partition.source_path; - partition_size_ = partition.source_size; - break; - case VerifierStep::kVerifyTargetHash: - part_path = partition.target_path; - partition_size_ = partition.target_size; - break; - } - - if (part_path.empty()) { - if (partition_size_ == 0) { - LOG(INFO) << "Skip hashing partition " << partition_index_ << " (" - << partition.name << ") because size is 0."; - partition_index_++; - StartPartitionHashing(); - return; + if (!should_write_verity) { + // In VABC, we cannot map/unmap partitions w/o first closing ALL fds first. + // Since this function might be called inside a ScheduledTask, the closure + // might have a copy of partition_fd_ when executing this function. Which + // means even if we do |partition_fd_.reset()| here, there's a chance that + // underlying fd isn't closed until we return. This is unacceptable, we need + // to close |partition_fd| right away. + if (partition_fd_) { + partition_fd_->Close(); + partition_fd_.reset(); } - LOG(ERROR) << "Cannot hash partition " << partition_index_ << " (" - << partition.name - << ") because its device path cannot be determined."; - Cleanup(ErrorCode::kFilesystemVerifierError); - return; + // In VABC, if we are not writing verity, just map all partitions, + // and read using regular fd on |postinstall_mount_device| . + // All read will go through snapuserd, which provides a consistent + // view: device will use snapuserd to read partition during boot. + // b/186196758 + // Call UnmapAllPartitions() first, because if we wrote verity before, these + // writes won't be visible to previously opened snapuserd daemon. To ensure + // that we will see the most up to date data from partitions, call Unmap() + // then Map() to re-spin daemon. + dynamic_control_->UnmapAllPartitions(); + dynamic_control_->MapAllPartitions(); + return InitializeFd(partition.readonly_target_path); } - - LOG(INFO) << "Hashing partition " << partition_index_ << " (" - << partition.name << ") on device " << part_path; - - brillo::ErrorPtr error; - src_stream_ = - brillo::FileStream::Open(base::FilePath(part_path), - brillo::Stream::AccessMode::READ, - brillo::FileStream::Disposition::OPEN_EXISTING, - &error); - - if (!src_stream_) { - LOG(ERROR) << "Unable to open " << part_path << " for reading"; - Cleanup(ErrorCode::kFilesystemVerifierError); - return; + partition_fd_ = + dynamic_control_->OpenCowFd(partition.name, partition.source_path, true); + if (!partition_fd_) { + LOG(ERROR) << "OpenCowReader(" << partition.name << ", " + << partition.source_path << ") failed."; + return false; } + partition_size_ = partition.target_size; + return true; +} - buffer_.resize(kReadFileBufferSize); - hasher_ = std::make_unique<HashCalculator>(); +bool FilesystemVerifierAction::InitializeFd(const std::string& part_path) { + partition_fd_ = FileDescriptorPtr(new EintrSafeFileDescriptor()); + const bool write_verity = ShouldWriteVerity(); + int flags = write_verity ? O_RDWR : O_RDONLY; + if (!utils::SetBlockDeviceReadOnly(part_path, !write_verity)) { + LOG(WARNING) << "Failed to set block device " << part_path << " as " + << (write_verity ? "writable" : "readonly"); + } + if (!partition_fd_->Open(part_path.c_str(), flags)) { + LOG(ERROR) << "Unable to open " << part_path << " for reading."; + return false; + } + return true; +} - offset_ = 0; - if (verifier_step_ == VerifierStep::kVerifyTargetHash && - install_plan_.write_verity) { - if (!verity_writer_->Init(partition)) { +void FilesystemVerifierAction::WriteVerityAndHashPartition( + FileDescriptorPtr fd, + const off64_t start_offset, + const off64_t end_offset, + void* buffer, + const size_t buffer_size) { + if (start_offset >= end_offset) { + LOG_IF(WARNING, start_offset > end_offset) + << "start_offset is greater than end_offset : " << start_offset << " > " + << end_offset; + if (!verity_writer_->Finalize(fd, fd)) { + LOG(ERROR) << "Failed to write verity data"; Cleanup(ErrorCode::kVerityCalculationError); return; } + if (dynamic_control_->UpdateUsesSnapshotCompression()) { + // Spin up snapuserd to read fs. + if (!InitializeFdVABC(false)) { + LOG(ERROR) << "Failed to map all partitions"; + Cleanup(ErrorCode::kFilesystemVerifierError); + return; + } + } + HashPartition(partition_fd_, 0, partition_size_, buffer, buffer_size); + return; } - - // Start the first read. - ScheduleRead(); + const auto cur_offset = fd->Seek(start_offset, SEEK_SET); + if (cur_offset != start_offset) { + PLOG(ERROR) << "Failed to seek to offset: " << start_offset; + Cleanup(ErrorCode::kVerityCalculationError); + return; + } + const auto read_size = + std::min<size_t>(buffer_size, end_offset - start_offset); + const auto bytes_read = fd->Read(buffer, read_size); + if (bytes_read < 0 || static_cast<size_t>(bytes_read) != read_size) { + PLOG(ERROR) << "Failed to read offset " << start_offset << " expected " + << read_size << " bytes, actual: " << bytes_read; + Cleanup(ErrorCode::kVerityCalculationError); + return; + } + if (!verity_writer_->Update( + start_offset, static_cast<const uint8_t*>(buffer), read_size)) { + LOG(ERROR) << "VerityWriter::Update() failed"; + Cleanup(ErrorCode::kVerityCalculationError); + return; + } + UpdatePartitionProgress((start_offset + bytes_read) * 1.0f / partition_size_ * + kVerityProgressPercent); + CHECK(pending_task_id_.PostTask( + FROM_HERE, + base::BindOnce(&FilesystemVerifierAction::WriteVerityAndHashPartition, + base::Unretained(this), + fd, + start_offset + bytes_read, + end_offset, + buffer, + buffer_size))); } -void FilesystemVerifierAction::ScheduleRead() { - const InstallPlan::Partition& partition = - install_plan_.partitions[partition_index_]; - - // We can only start reading anything past |hash_tree_offset| after we have - // already read all the data blocks that the hash tree covers. The same - // applies to FEC. - uint64_t read_end = partition_size_; - if (partition.hash_tree_size != 0 && - offset_ < partition.hash_tree_data_offset + partition.hash_tree_data_size) - read_end = std::min(read_end, partition.hash_tree_offset); - if (partition.fec_size != 0 && - offset_ < partition.fec_data_offset + partition.fec_data_size) - read_end = std::min(read_end, partition.fec_offset); - size_t bytes_to_read = - std::min(static_cast<uint64_t>(buffer_.size()), read_end - offset_); - if (!bytes_to_read) { +void FilesystemVerifierAction::HashPartition(FileDescriptorPtr fd, + const off64_t start_offset, + const off64_t end_offset, + void* buffer, + const size_t buffer_size) { + if (start_offset >= end_offset) { + LOG_IF(WARNING, start_offset > end_offset) + << "start_offset is greater than end_offset : " << start_offset << " > " + << end_offset; FinishPartitionHashing(); return; } - - bool read_async_ok = src_stream_->ReadAsync( - buffer_.data(), - bytes_to_read, - base::Bind(&FilesystemVerifierAction::OnReadDoneCallback, - base::Unretained(this)), - base::Bind(&FilesystemVerifierAction::OnReadErrorCallback, - base::Unretained(this)), - nullptr); - - if (!read_async_ok) { - LOG(ERROR) << "Unable to schedule an asynchronous read from the stream."; - Cleanup(ErrorCode::kError); + const auto cur_offset = fd->Seek(start_offset, SEEK_SET); + if (cur_offset != start_offset) { + PLOG(ERROR) << "Failed to seek to offset: " << start_offset; + Cleanup(ErrorCode::kFilesystemVerifierError); + return; + } + const auto read_size = + std::min<size_t>(buffer_size, end_offset - start_offset); + const auto bytes_read = fd->Read(buffer, read_size); + if (bytes_read < 0 || static_cast<size_t>(bytes_read) != read_size) { + PLOG(ERROR) << "Failed to read offset " << start_offset << " expected " + << read_size << " bytes, actual: " << bytes_read; + Cleanup(ErrorCode::kFilesystemVerifierError); + return; + } + if (!hasher_->Update(buffer, read_size)) { + LOG(ERROR) << "Hasher updated failed on offset" << start_offset; + Cleanup(ErrorCode::kFilesystemVerifierError); + return; } + const auto progress = (start_offset + bytes_read) * 1.0f / partition_size_; + UpdatePartitionProgress(progress * (1 - kVerityProgressPercent) + + kVerityProgressPercent); + CHECK(pending_task_id_.PostTask( + FROM_HERE, + base::BindOnce(&FilesystemVerifierAction::HashPartition, + base::Unretained(this), + fd, + start_offset + bytes_read, + end_offset, + buffer, + buffer_size))); } -void FilesystemVerifierAction::OnReadDoneCallback(size_t bytes_read) { - if (cancelled_) { - Cleanup(ErrorCode::kError); +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; } + const InstallPlan::Partition& partition = + install_plan_.partitions[partition_index_]; + const auto& part_path = GetPartitionPath(); + partition_size_ = GetPartitionSize(); - if (bytes_read == 0) { - LOG(ERROR) << "Failed to read the remaining " << partition_size_ - offset_ - << " bytes from partition " - << install_plan_.partitions[partition_index_].name; + LOG(INFO) << "Hashing partition " << partition_index_ << " (" + << partition.name << ") on device " << part_path; + auto success = false; + if (IsVABC(partition)) { + success = InitializeFdVABC(ShouldWriteVerity()); + } else { + if (part_path.empty()) { + if (partition_size_ == 0) { + LOG(INFO) << "Skip hashing partition " << partition_index_ << " (" + << partition.name << ") because size is 0."; + partition_index_++; + StartPartitionHashing(); + return; + } + LOG(ERROR) << "Cannot hash partition " << partition_index_ << " (" + << partition.name + << ") because its device path cannot be determined."; + Cleanup(ErrorCode::kFilesystemVerifierError); + return; + } + success = InitializeFd(part_path); + } + if (!success) { Cleanup(ErrorCode::kFilesystemVerifierError); return; } + buffer_.resize(kReadFileBufferSize); + hasher_ = std::make_unique<HashCalculator>(); - if (!hasher_->Update(buffer_.data(), bytes_read)) { - LOG(ERROR) << "Unable to update the hash."; - Cleanup(ErrorCode::kError); - return; + offset_ = 0; + filesystem_data_end_ = partition_size_; + if (partition.fec_offset > 0) { + CHECK_LE(partition.hash_tree_offset, partition.fec_offset) + << " Hash tree is expected to come before FEC data"; } - - if (verifier_step_ == VerifierStep::kVerifyTargetHash && - install_plan_.write_verity) { - if (!verity_writer_->Update(offset_, buffer_.data(), bytes_read)) { + if (partition.hash_tree_offset != 0) { + filesystem_data_end_ = partition.hash_tree_offset; + } else if (partition.fec_offset != 0) { + filesystem_data_end_ = partition.fec_offset; + } + if (ShouldWriteVerity()) { + LOG(INFO) << "Verity writes enabled on partition " << partition.name; + if (!verity_writer_->Init(partition)) { + LOG(INFO) << "Verity writes enabled on partition " << partition.name; Cleanup(ErrorCode::kVerityCalculationError); return; } + WriteVerityAndHashPartition( + partition_fd_, 0, filesystem_data_end_, buffer_.data(), buffer_.size()); + } else { + LOG(INFO) << "Verity writes disabled on partition " << partition.name; + HashPartition( + partition_fd_, 0, partition_size_, buffer_.data(), buffer_.size()); } +} - offset_ += bytes_read; +bool FilesystemVerifierAction::IsVABC( + const InstallPlan::Partition& partition) const { + return dynamic_control_->UpdateUsesSnapshotCompression() && + verifier_step_ == VerifierStep::kVerifyTargetHash && + dynamic_control_->IsDynamicPartition(partition.name, + install_plan_.target_slot); +} - if (offset_ == partition_size_) { - FinishPartitionHashing(); - return; +const std::string& FilesystemVerifierAction::GetPartitionPath() const { + const InstallPlan::Partition& partition = + install_plan_.partitions[partition_index_]; + switch (verifier_step_) { + case VerifierStep::kVerifySourceHash: + return partition.source_path; + case VerifierStep::kVerifyTargetHash: + if (IsVABC(partition)) { + return partition.readonly_target_path; + } else { + return partition.target_path; + } } +} - ScheduleRead(); +size_t FilesystemVerifierAction::GetPartitionSize() const { + const InstallPlan::Partition& partition = + install_plan_.partitions[partition_index_]; + switch (verifier_step_) { + case VerifierStep::kVerifySourceHash: + return partition.source_size; + case VerifierStep::kVerifyTargetHash: + return partition.target_size; + } } -void FilesystemVerifierAction::OnReadErrorCallback(const brillo::Error* error) { - // TODO(deymo): Transform the read-error into an specific ErrorCode. - LOG(ERROR) << "Asynchronous read failed."; - Cleanup(ErrorCode::kError); +bool FilesystemVerifierAction::ShouldWriteVerity() { + const InstallPlan::Partition& partition = + install_plan_.partitions[partition_index_]; + return verifier_step_ == VerifierStep::kVerifyTargetHash && + install_plan_.write_verity && + (partition.hash_tree_size > 0 || partition.fec_size > 0); } void FilesystemVerifierAction::FinishPartitionHashing() { @@ -249,8 +448,8 @@ void FilesystemVerifierAction::FinishPartitionHashing() { } // If we have not verified source partition yet, now that the target // partition does not match, and it's not a full payload, we need to - // switch to kVerifySourceHash step to check if it's because the source - // partition does not match either. + // switch to kVerifySourceHash step to check if it's because the + // source partition does not match either. verifier_step_ = VerifierStep::kVerifySourceHash; } else { partition_index_++; @@ -286,17 +485,20 @@ void FilesystemVerifierAction::FinishPartitionHashing() { } // The action will skip kVerifySourceHash step if target partition hash // matches, if we are in this step, it means target hash does not match, - // and now that the source partition hash matches, we should set the error - // code to reflect the error in target partition. - // We only need to verify the source partition which the target hash does - // not match, the rest of the partitions don't matter. + // and now that the source partition hash matches, we should set the + // error code to reflect the error in target partition. We only need to + // verify the source partition which the target hash does not match, the + // rest of the partitions don't matter. Cleanup(ErrorCode::kNewRootfsVerificationError); return; } // Start hashing the next partition, if any. hasher_.reset(); buffer_.clear(); - src_stream_->CloseBlocking(nullptr); + if (partition_fd_) { + partition_fd_->Close(); + partition_fd_.reset(); + } StartPartitionHashing(); } diff --git a/payload_consumer/filesystem_verifier_action.h b/payload_consumer/filesystem_verifier_action.h index 83d66687..850abdad 100644 --- a/payload_consumer/filesystem_verifier_action.h +++ b/payload_consumer/filesystem_verifier_action.h @@ -22,12 +22,15 @@ #include <memory> #include <string> +#include <utility> #include <vector> -#include <brillo/streams/stream.h> +#include <brillo/message_loops/message_loop.h> #include "update_engine/common/action.h" #include "update_engine/common/hash_calculator.h" +#include "update_engine/common/scoped_task_id.h" +#include "update_engine/payload_consumer/file_descriptor.h" #include "update_engine/payload_consumer/install_plan.h" #include "update_engine/payload_consumer/verity_writer_interface.h" @@ -49,32 +52,62 @@ 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(); } private: friend class FilesystemVerifierActionTestDelegate; + void WriteVerityAndHashPartition(FileDescriptorPtr fd, + const off64_t start_offset, + const off64_t end_offset, + void* buffer, + const size_t buffer_size); + void HashPartition(FileDescriptorPtr fd, + const off64_t start_offset, + const off64_t end_offset, + void* buffer, + const size_t buffer_size); + + // Return true if we need to write verity bytes. + bool ShouldWriteVerity(); // Starts the hashing of the current partition. If there aren't any partitions // remaining to be hashed, it finishes the action. void StartPartitionHashing(); - // Schedules the asynchronous read of the filesystem. - void ScheduleRead(); + const std::string& GetPartitionPath() const; + + bool IsVABC(const InstallPlan::Partition& partition) const; - // Called from the main loop when a single read from |src_stream_| succeeds or - // fails, calling OnReadDoneCallback() and OnReadErrorCallback() respectively. - void OnReadDoneCallback(size_t bytes_read); - void OnReadErrorCallback(const brillo::Error* error); + size_t GetPartitionSize() const; // When the read is done, finalize the hash checking of the current partition // and continue checking the next one. @@ -85,6 +118,17 @@ 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); + + // Updates progress of current partition. |progress| should be in range [0, + // 1], and it will be scaled appropriately with # of partitions. + void UpdatePartitionProgress(double progress); + + // Initialize read_fd_ and write_fd_ + bool InitializeFd(const std::string& part_path); + bool InitializeFdVABC(bool should_write_verity); + // The type of the partition that we are verifying. VerifierStep verifier_step_ = VerifierStep::kVerifyTargetHash; @@ -92,23 +136,24 @@ class FilesystemVerifierAction : public InstallPlanAction { // being hashed. size_t partition_index_{0}; - // If not null, the FileStream used to read from the device. - brillo::StreamPtr src_stream_; + // If not null, the FileDescriptor used to read from the device. + // verity writer might attempt to write to this fd, if verity is enabled. + FileDescriptorPtr partition_fd_; // Buffer for storing data we read. brillo::Blob buffer_; 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 +164,16 @@ class FilesystemVerifierAction : public InstallPlanAction { // The byte offset that we are reading in the current partition. uint64_t offset_{0}; + // The end offset of filesystem data, first byte position of hashtree. + uint64_t filesystem_data_end_{0}; + + // An observer that observes progress updates of this action. + FilesystemVerifyDelegate* delegate_{}; + + // Callback that should be cancelled on |TerminateProcessing|. Usually this + // points to pending read callbacks from async stream. + ScopedTaskId pending_task_id_; + DISALLOW_COPY_AND_ASSIGN(FilesystemVerifierAction); }; diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc index cb33404d..f2f29547 100644 --- a/payload_consumer/filesystem_verifier_action_unittest.cc +++ b/payload_consumer/filesystem_verifier_action_unittest.cc @@ -16,6 +16,8 @@ #include "update_engine/payload_consumer/filesystem_verifier_action.h" +#include <algorithm> +#include <cstring> #include <memory> #include <string> #include <utility> @@ -25,34 +27,194 @@ #include <brillo/message_loops/fake_message_loop.h> #include <brillo/message_loops/message_loop_utils.h> #include <brillo/secure_blob.h> +#include <fec/ecc.h> #include <gtest/gtest.h> +#include <libsnapshot/snapshot_writer.h> +#include <sys/stat.h> +#include "update_engine/common/dynamic_partition_control_stub.h" #include "update_engine/common/hash_calculator.h" +#include "update_engine/common/mock_dynamic_partition_control.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" +#include "update_engine/payload_consumer/fake_file_descriptor.h" +#include "update_engine/payload_consumer/install_plan.h" +#include "update_engine/payload_consumer/verity_writer_android.h" using brillo::MessageLoop; using std::string; +using testing::_; +using testing::AtLeast; +using testing::DoAll; +using testing::NiceMock; +using testing::Return; +using testing::SetArgPointee; namespace chromeos_update_engine { class FilesystemVerifierActionTest : public ::testing::Test { + public: + static constexpr size_t BLOCK_SIZE = 4096; + // We use SHA256 for testing, so hash size is 256bits / 8 + static constexpr size_t HASH_SIZE = 256 / 8; + static constexpr size_t PARTITION_SIZE = BLOCK_SIZE * 1024; + static constexpr size_t HASH_TREE_START_OFFSET = 800 * BLOCK_SIZE; + size_t hash_tree_size = 0; + size_t fec_start_offset = 0; + size_t fec_data_size = 0; + static constexpr size_t FEC_ROOTS = 2; + size_t fec_rounds = 0; + size_t fec_size = 0; + protected: - void SetUp() override { loop_.SetAsCurrent(); } + void SetUp() override { + hash_tree_size = HashTreeBuilder::CalculateSize( + HASH_TREE_START_OFFSET, BLOCK_SIZE, HASH_SIZE); + fec_start_offset = HASH_TREE_START_OFFSET + hash_tree_size; + fec_data_size = fec_start_offset; + static constexpr size_t FEC_ROOTS = 2; + fec_rounds = + utils::DivRoundUp(fec_data_size / BLOCK_SIZE, FEC_RSM - FEC_ROOTS); + fec_size = fec_rounds * FEC_ROOTS * BLOCK_SIZE; + + fec_data_.resize(fec_size); + hash_tree_data_.resize(hash_tree_size); + // Globally readable writable, as we want to write data + ASSERT_EQ(0, fchmod(source_part_.fd(), 0666)) + << " Failed to set " << source_part_.path() << " as writable " + << strerror(errno); + ASSERT_EQ(0, fchmod(target_part_.fd(), 0666)) + << " Failed to set " << target_part_.path() << " as writable " + << strerror(errno); + brillo::Blob part_data(PARTITION_SIZE); + test_utils::FillWithData(&part_data); + ASSERT_TRUE(utils::WriteFile( + source_part_.path().c_str(), part_data.data(), part_data.size())); + // FillWithData() will fill with different data next call. We want + // source/target partitions to contain different data for testing. + test_utils::FillWithData(&part_data); + ASSERT_TRUE(utils::WriteFile( + target_part_.path().c_str(), part_data.data(), part_data.size())); + loop_.SetAsCurrent(); + } void TearDown() override { EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1)); } + void DoTestVABC(bool clear_target_hash, bool enable_verity); + // Returns true iff test has completed successfully. bool DoTest(bool terminate_early, bool hash_fail); void BuildActions(const InstallPlan& install_plan); + void BuildActions(const InstallPlan& install_plan, + DynamicPartitionControlInterface* dynamic_control); + + InstallPlan::Partition* AddFakePartition(InstallPlan* install_plan, + std::string name = "fake_part") { + InstallPlan::Partition& part = install_plan->partitions.emplace_back(); + part.name = name; + part.target_path = target_part_.path(); + part.readonly_target_path = part.target_path; + part.target_size = PARTITION_SIZE; + part.block_size = BLOCK_SIZE; + part.source_path = source_part_.path(); + part.source_size = PARTITION_SIZE; + EXPECT_TRUE( + HashCalculator::RawHashOfFile(source_part_.path(), &part.source_hash)); + EXPECT_TRUE( + HashCalculator::RawHashOfFile(target_part_.path(), &part.target_hash)); + return ∂ + } + static void ZeroRange(FileDescriptorPtr fd, + size_t start_block, + size_t num_blocks) { + std::vector<unsigned char> buffer(BLOCK_SIZE); + ASSERT_EQ((ssize_t)(start_block * BLOCK_SIZE), + fd->Seek(start_block * BLOCK_SIZE, SEEK_SET)); + for (size_t i = 0; i < num_blocks; i++) { + ASSERT_TRUE(utils::WriteAll(fd, buffer.data(), buffer.size())); + } + } + + void SetHashWithVerity(InstallPlan::Partition* partition) { + partition->hash_tree_algorithm = "sha256"; + partition->hash_tree_size = hash_tree_size; + partition->hash_tree_offset = HASH_TREE_START_OFFSET; + partition->hash_tree_data_offset = 0; + partition->hash_tree_data_size = HASH_TREE_START_OFFSET; + partition->fec_size = fec_size; + partition->fec_offset = fec_start_offset; + partition->fec_data_offset = 0; + partition->fec_data_size = fec_data_size; + partition->fec_roots = FEC_ROOTS; + VerityWriterAndroid verity_writer; + ASSERT_TRUE(verity_writer.Init(*partition)); + LOG(INFO) << "Opening " << partition->readonly_target_path; + auto fd = std::make_shared<EintrSafeFileDescriptor>(); + ASSERT_TRUE(fd->Open(partition->readonly_target_path.c_str(), O_RDWR)) + << "Failed to open " << partition->target_path.c_str() << " " + << strerror(errno); + std::vector<unsigned char> buffer(BLOCK_SIZE); + // Only need to read up to hash tree + auto bytes_to_read = HASH_TREE_START_OFFSET; + auto offset = 0; + while (bytes_to_read > 0) { + const auto bytes_read = fd->Read( + buffer.data(), std::min<size_t>(buffer.size(), bytes_to_read)); + ASSERT_GT(bytes_read, 0) + << "offset: " << offset << " bytes to read: " << bytes_to_read + << " error: " << strerror(errno); + ASSERT_TRUE(verity_writer.Update(offset, buffer.data(), bytes_read)); + bytes_to_read -= bytes_read; + offset += bytes_read; + } + ASSERT_TRUE(verity_writer.Finalize(fd, fd)); + ASSERT_TRUE(fd->IsOpen()); + ASSERT_TRUE(HashCalculator::RawHashOfFile(target_part_.path(), + &partition->target_hash)); + + ASSERT_TRUE(fd->Seek(HASH_TREE_START_OFFSET, SEEK_SET)); + ASSERT_EQ(fd->Read(hash_tree_data_.data(), hash_tree_data_.size()), + static_cast<ssize_t>(hash_tree_data_.size())) + << "Failed to read hashtree " << strerror(errno); + ASSERT_TRUE(fd->Seek(fec_start_offset, SEEK_SET)); + ASSERT_EQ(fd->Read(fec_data_.data(), fec_data_.size()), + static_cast<ssize_t>(fec_data_.size())) + << "Failed to read FEC " << strerror(errno); + // Fs verification action is expected to write them, so clear verity data to + // ensure that they are re-created correctly. + ZeroRange( + fd, HASH_TREE_START_OFFSET / BLOCK_SIZE, hash_tree_size / BLOCK_SIZE); + ZeroRange(fd, fec_start_offset / BLOCK_SIZE, fec_size / BLOCK_SIZE); + } brillo::FakeMessageLoop loop_{nullptr}; ActionProcessor processor_; + DynamicPartitionControlStub dynamic_control_stub_; + std::vector<unsigned char> fec_data_; + std::vector<unsigned char> hash_tree_data_; + static ScopedTempFile source_part_; + static ScopedTempFile target_part_; + InstallPlan install_plan_; }; +ScopedTempFile FilesystemVerifierActionTest::source_part_{ + "source_part.XXXXXX", true, PARTITION_SIZE}; +ScopedTempFile FilesystemVerifierActionTest::target_part_{ + "target_part.XXXXXX", true, PARTITION_SIZE}; + +static void EnableVABC(MockDynamicPartitionControl* dynamic_control, + const std::string& part_name) { + ON_CALL(*dynamic_control, GetDynamicPartitionsFeatureFlag()) + .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH))); + ON_CALL(*dynamic_control, UpdateUsesSnapshotCompression()) + .WillByDefault(Return(true)); + ON_CALL(*dynamic_control, IsDynamicPartition(part_name, _)) + .WillByDefault(Return(true)); +} + class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate { public: FilesystemVerifierActionTestDelegate() @@ -70,7 +232,8 @@ class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate { if (action->Type() == FilesystemVerifierAction::StaticType()) { ran_ = true; code_ = code; - EXPECT_FALSE(static_cast<FilesystemVerifierAction*>(action)->src_stream_); + EXPECT_FALSE( + static_cast<FilesystemVerifierAction*>(action)->partition_fd_); } else if (action->Type() == ObjectCollectorAction<InstallPlan>::StaticType()) { auto collector_action = @@ -90,7 +253,7 @@ class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate { bool FilesystemVerifierActionTest::DoTest(bool terminate_early, bool hash_fail) { - test_utils::ScopedTempFile a_loop_file("a_loop_file.XXXXXX"); + ScopedTempFile a_loop_file("a_loop_file.XXXXXX"); // Make random data for a. const size_t kLoopFileSize = 10 * 1024 * 1024 + 512; @@ -117,9 +280,8 @@ bool FilesystemVerifierActionTest::DoTest(bool terminate_early, bool success = true; // Set up the action objects - InstallPlan install_plan; - install_plan.source_slot = 0; - install_plan.target_slot = 1; + install_plan_.source_slot = 0; + install_plan_.target_slot = 1; InstallPlan::Partition part; part.name = "part"; part.target_size = kLoopFileSize - (hash_fail ? 1 : 0); @@ -134,23 +296,19 @@ bool FilesystemVerifierActionTest::DoTest(bool terminate_early, ADD_FAILURE(); success = false; } - install_plan.partitions = {part}; + install_plan_.partitions = {part}; - BuildActions(install_plan); + BuildActions(install_plan_); FilesystemVerifierActionTestDelegate delegate; processor_.set_delegate(&delegate); - loop_.PostTask(FROM_HERE, - base::Bind( - [](ActionProcessor* processor, bool terminate_early) { - processor->StartProcessing(); - if (terminate_early) { - processor->StopProcessing(); - } - }, - base::Unretained(&processor_), - terminate_early)); + loop_.PostTask(base::Bind(&ActionProcessor::StartProcessing, + base::Unretained(&processor_))); + if (terminate_early) { + loop_.PostTask(base::Bind(&ActionProcessor::StopProcessing, + base::Unretained(&processor_))); + } loop_.Run(); if (!terminate_early) { @@ -179,16 +337,18 @@ bool FilesystemVerifierActionTest::DoTest(bool terminate_early, EXPECT_TRUE(is_a_file_reading_eq); success = success && is_a_file_reading_eq; - bool is_install_plan_eq = (*delegate.install_plan_ == install_plan); + bool is_install_plan_eq = (*delegate.install_plan_ == install_plan_); EXPECT_TRUE(is_install_plan_eq); success = success && is_install_plan_eq; return success; } void FilesystemVerifierActionTest::BuildActions( - const InstallPlan& install_plan) { + const InstallPlan& install_plan, + DynamicPartitionControlInterface* dynamic_control) { auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>(); - auto verifier_action = std::make_unique<FilesystemVerifierAction>(); + auto verifier_action = + std::make_unique<FilesystemVerifierAction>(dynamic_control); auto collector_action = std::make_unique<ObjectCollectorAction<InstallPlan>>(); @@ -202,6 +362,11 @@ void FilesystemVerifierActionTest::BuildActions( processor_.EnqueueAction(std::move(collector_action)); } +void FilesystemVerifierActionTest::BuildActions( + const InstallPlan& install_plan) { + BuildActions(install_plan, &dynamic_control_stub_); +} + class FilesystemVerifierActionTest2Delegate : public ActionProcessorDelegate { public: void ActionCompleted(ActionProcessor* processor, @@ -217,7 +382,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>>(); @@ -230,20 +396,19 @@ TEST_F(FilesystemVerifierActionTest, MissingInputObjectTest) { processor_.set_delegate(&delegate); processor_.StartProcessing(); - EXPECT_FALSE(processor_.IsRunning()); - EXPECT_TRUE(delegate.ran_); + ASSERT_FALSE(processor_.IsRunning()); + ASSERT_TRUE(delegate.ran_); EXPECT_EQ(ErrorCode::kError, delegate.code_); } TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) { - InstallPlan install_plan; InstallPlan::Partition part; part.name = "nope"; part.source_path = "/no/such/file"; part.target_path = "/no/such/file"; - install_plan.partitions = {part}; + install_plan_.partitions = {part}; - BuildActions(install_plan); + BuildActions(install_plan_); FilesystemVerifierActionTest2Delegate delegate; processor_.set_delegate(&delegate); @@ -274,7 +439,7 @@ TEST_F(FilesystemVerifierActionTest, RunAsRootTerminateEarlyTest) { #ifdef __ANDROID__ TEST_F(FilesystemVerifierActionTest, RunAsRootWriteVerityTest) { - test_utils::ScopedTempFile part_file("part_file.XXXXXX"); + ScopedTempFile part_file("part_file.XXXXXX"); constexpr size_t filesystem_size = 200 * 4096; constexpr size_t part_size = 256 * 4096; brillo::Blob part_data(filesystem_size, 0x1); @@ -284,7 +449,6 @@ TEST_F(FilesystemVerifierActionTest, RunAsRootWriteVerityTest) { test_utils::ScopedLoopbackDeviceBinder target_device( part_file.path(), true, &target_path); - InstallPlan install_plan; InstallPlan::Partition part; part.name = "part"; part.target_path = target_path; @@ -315,9 +479,9 @@ TEST_F(FilesystemVerifierActionTest, RunAsRootWriteVerityTest) { part.hash_tree_salt = {0x9e, 0xcb, 0xf8, 0xd5, 0x0b, 0xb4, 0x43, 0x0a, 0x7a, 0x10, 0xad, 0x96, 0xd7, 0x15, 0x70, 0xba, 0xed, 0x27, 0xe2, 0xae}; - install_plan.partitions = {part}; + install_plan_.partitions = {part}; - BuildActions(install_plan); + BuildActions(install_plan_); FilesystemVerifierActionTestDelegate delegate; processor_.set_delegate(&delegate); @@ -336,7 +500,7 @@ TEST_F(FilesystemVerifierActionTest, RunAsRootWriteVerityTest) { #endif // __ANDROID__ TEST_F(FilesystemVerifierActionTest, RunAsRootSkipWriteVerityTest) { - test_utils::ScopedTempFile part_file("part_file.XXXXXX"); + ScopedTempFile part_file("part_file.XXXXXX"); constexpr size_t filesystem_size = 200 * 4096; constexpr size_t part_size = 256 * 4096; brillo::Blob part_data(part_size); @@ -346,8 +510,7 @@ TEST_F(FilesystemVerifierActionTest, RunAsRootSkipWriteVerityTest) { test_utils::ScopedLoopbackDeviceBinder target_device( part_file.path(), true, &target_path); - InstallPlan install_plan; - install_plan.write_verity = false; + install_plan_.write_verity = false; InstallPlan::Partition part; part.name = "part"; part.target_path = target_path; @@ -362,9 +525,9 @@ TEST_F(FilesystemVerifierActionTest, RunAsRootSkipWriteVerityTest) { part.fec_offset = part.fec_data_size; part.fec_size = 2 * 4096; EXPECT_TRUE(HashCalculator::RawHashOfData(part_data, &part.target_hash)); - install_plan.partitions = {part}; + install_plan_.partitions = {part}; - BuildActions(install_plan); + BuildActions(install_plan_); FilesystemVerifierActionTestDelegate delegate; processor_.set_delegate(&delegate); @@ -376,8 +539,151 @@ TEST_F(FilesystemVerifierActionTest, RunAsRootSkipWriteVerityTest) { base::Unretained(&processor_))); loop_.Run(); - EXPECT_FALSE(processor_.IsRunning()); - EXPECT_TRUE(delegate.ran()); - EXPECT_EQ(ErrorCode::kSuccess, delegate.code()); + ASSERT_FALSE(processor_.IsRunning()); + ASSERT_TRUE(delegate.ran()); + ASSERT_EQ(ErrorCode::kSuccess, delegate.code()); } + +void FilesystemVerifierActionTest::DoTestVABC(bool clear_target_hash, + bool enable_verity) { + auto part_ptr = AddFakePartition(&install_plan_); + if (::testing::Test::HasFailure()) { + return; + } + ASSERT_NE(part_ptr, nullptr); + InstallPlan::Partition& part = *part_ptr; + part.target_path = "Shouldn't attempt to open this path"; + if (enable_verity) { + install_plan_.write_verity = true; + ASSERT_NO_FATAL_FAILURE(SetHashWithVerity(&part)); + } + if (clear_target_hash) { + part.target_hash.clear(); + } + + NiceMock<MockDynamicPartitionControl> dynamic_control; + + EnableVABC(&dynamic_control, part.name); + auto open_cow = [part]() { + auto cow_fd = std::make_shared<EintrSafeFileDescriptor>(); + EXPECT_TRUE(cow_fd->Open(part.readonly_target_path.c_str(), O_RDWR)) + << "Failed to open part " << part.readonly_target_path + << strerror(errno); + return cow_fd; + }; + + EXPECT_CALL(dynamic_control, UpdateUsesSnapshotCompression()) + .Times(AtLeast(1)); + auto cow_fd = open_cow(); + if (HasFailure()) { + return; + } + + if (enable_verity) { + ON_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _)) + .WillByDefault(open_cow); + EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _)) + .Times(AtLeast(1)); + + // fs verification isn't supposed to write to |readonly_target_path|. All + // writes should go through fd returned by |OpenCowFd|. Therefore we set + // target part as read-only to make sure. + ASSERT_EQ(0, chmod(part.readonly_target_path.c_str(), 0444)) + << " Failed to set " << part.readonly_target_path << " as read-only " + << strerror(errno); + } else { + // Since we are not writing verity, we should not attempt to OpenCowFd() + // reads should go through regular file descriptors on mapped partitions. + EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _)) + .Times(0); + EXPECT_CALL(dynamic_control, MapAllPartitions()).Times(AtLeast(1)); + } + EXPECT_CALL(dynamic_control, ListDynamicPartitionsForSlot(_, _, _)) + .WillRepeatedly( + DoAll(SetArgPointee<2, std::vector<std::string>>({part.name}), + Return(true))); + + BuildActions(install_plan_, &dynamic_control); + + FilesystemVerifierActionTestDelegate delegate; + processor_.set_delegate(&delegate); + + loop_.PostTask(FROM_HERE, + base::Bind(&ActionProcessor::StartProcessing, + base::Unretained(&processor_))); + loop_.Run(); + + ASSERT_FALSE(processor_.IsRunning()); + ASSERT_TRUE(delegate.ran()); + if (enable_verity) { + std::vector<unsigned char> actual_fec(fec_size); + ssize_t bytes_read = 0; + ASSERT_TRUE(utils::PReadAll(cow_fd, + actual_fec.data(), + actual_fec.size(), + fec_start_offset, + &bytes_read)); + ASSERT_EQ(actual_fec, fec_data_); + std::vector<unsigned char> actual_hash_tree(hash_tree_size); + ASSERT_TRUE(utils::PReadAll(cow_fd, + actual_hash_tree.data(), + actual_hash_tree.size(), + HASH_TREE_START_OFFSET, + &bytes_read)); + ASSERT_EQ(actual_hash_tree, hash_tree_data_); + } + if (clear_target_hash) { + ASSERT_EQ(ErrorCode::kNewRootfsVerificationError, delegate.code()); + } else { + ASSERT_EQ(ErrorCode::kSuccess, delegate.code()); + } +} + +TEST_F(FilesystemVerifierActionTest, VABC_NoVerity_Success) { + DoTestVABC(false, false); +} + +TEST_F(FilesystemVerifierActionTest, VABC_NoVerity_Target_Mismatch) { + DoTestVABC(true, false); +} + +TEST_F(FilesystemVerifierActionTest, VABC_Verity_Success) { + DoTestVABC(false, true); +} + +TEST_F(FilesystemVerifierActionTest, VABC_Verity_ReadAfterWrite) { + ASSERT_NO_FATAL_FAILURE(DoTestVABC(false, true)); + // Run FS verification again, w/o writing verity. We have seen a bug where + // attempting to run fs again will cause previously written verity data to be + // dropped, so cover this scenario. + ASSERT_GE(install_plan_.partitions.size(), 1UL); + auto& part = install_plan_.partitions[0]; + install_plan_.write_verity = false; + part.readonly_target_path = target_part_.path(); + NiceMock<MockDynamicPartitionControl> dynamic_control; + EnableVABC(&dynamic_control, part.name); + + // b/186196758 is only visible if we repeatedely run FS verification w/o + // writing verity + for (int i = 0; i < 3; i++) { + BuildActions(install_plan_, &dynamic_control); + + FilesystemVerifierActionTestDelegate delegate; + processor_.set_delegate(&delegate); + loop_.PostTask( + FROM_HERE, + base::Bind( + [](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(&processor_))); + loop_.Run(); + ASSERT_FALSE(processor_.IsRunning()); + ASSERT_TRUE(delegate.ran()); + ASSERT_EQ(ErrorCode::kSuccess, delegate.code()); + } +} + +TEST_F(FilesystemVerifierActionTest, VABC_Verity_Target_Mismatch) { + DoTestVABC(true, true); +} + } // namespace chromeos_update_engine diff --git a/payload_consumer/install_plan.cc b/payload_consumer/install_plan.cc index 766b27cf..06b7dd8c 100644 --- a/payload_consumer/install_plan.cc +++ b/payload_consumer/install_plan.cc @@ -16,6 +16,9 @@ #include "update_engine/payload_consumer/install_plan.h" +#include <algorithm> +#include <utility> + #include <base/format_macros.h> #include <base/logging.h> #include <base/strings/string_number_conversions.h> @@ -26,9 +29,29 @@ #include "update_engine/payload_consumer/payload_constants.h" using std::string; +using std::vector; namespace chromeos_update_engine { +namespace { +string PayloadUrlsToString( + const decltype(InstallPlan::Payload::payload_urls)& payload_urls) { + return "(" + base::JoinString(payload_urls, ",") + ")"; +} + +string VectorToString(const vector<std::pair<string, string>>& input, + const string& separator) { + vector<string> vec; + std::transform(input.begin(), + input.end(), + std::back_inserter(vec), + [](const auto& pair) { + return base::JoinString({pair.first, pair.second}, ": "); + }); + return base::JoinString(vec, separator); +} +} // namespace + string InstallPayloadTypeToString(InstallPayloadType type) { switch (type) { case InstallPayloadType::kUnknown: @@ -53,34 +76,10 @@ bool InstallPlan::operator!=(const InstallPlan& that) const { } void InstallPlan::Dump() const { - string partitions_str; - for (const auto& partition : partitions) { - partitions_str += - base::StringPrintf(", part: %s (source_size: %" PRIu64 - ", target_size %" PRIu64 ", postinst:%s)", - partition.name.c_str(), - partition.source_size, - partition.target_size, - utils::ToString(partition.run_postinstall).c_str()); - } - string payloads_str; - for (const auto& payload : payloads) { - payloads_str += base::StringPrintf( - ", payload: (size: %" PRIu64 ", metadata_size: %" PRIu64 - ", metadata signature: %s, hash: %s, payload type: %s)", - payload.size, - payload.metadata_size, - payload.metadata_signature.c_str(), - base::HexEncode(payload.hash.data(), payload.hash.size()).c_str(), - InstallPayloadTypeToString(payload.type).c_str()); - } - - string version_str = base::StringPrintf(", version: %s", version.c_str()); - if (!system_version.empty()) { - version_str += - base::StringPrintf(", system_version: %s", system_version.c_str()); - } + LOG(INFO) << "InstallPlan: \n" << ToString(); +} +string InstallPlan::ToString() const { string url_str = download_url; if (base::StartsWith( url_str, "fd://", base::CompareCase::INSENSITIVE_ASCII)) { @@ -88,19 +87,66 @@ void InstallPlan::Dump() const { 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) - << ", 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: " - << utils::ToString(switch_slot_on_reboot) - << ", run_post_install: " << utils::ToString(run_post_install) - << ", is_rollback: " << utils::ToString(is_rollback) - << ", write_verity: " << utils::ToString(write_verity); + vector<string> result_str; + result_str.emplace_back(VectorToString( + { + {"type", (is_resume ? "resume" : "new_update")}, + {"version", version}, + {"source_slot", BootControlInterface::SlotName(source_slot)}, + {"target_slot", BootControlInterface::SlotName(target_slot)}, + {"initial url", url_str}, + {"hash_checks_mandatory", utils::ToString(hash_checks_mandatory)}, + {"powerwash_required", utils::ToString(powerwash_required)}, + {"switch_slot_on_reboot", utils::ToString(switch_slot_on_reboot)}, + {"run_post_install", utils::ToString(run_post_install)}, + {"is_rollback", utils::ToString(is_rollback)}, + {"rollback_data_save_requested", + utils::ToString(rollback_data_save_requested)}, + {"write_verity", utils::ToString(write_verity)}, + }, + "\n")); + + for (const auto& partition : partitions) { + result_str.emplace_back(VectorToString( + { + {"Partition", partition.name}, + {"source_size", base::NumberToString(partition.source_size)}, + {"source_path", partition.source_path}, + {"source_hash", + base::HexEncode(partition.source_hash.data(), + partition.source_hash.size())}, + {"target_size", base::NumberToString(partition.target_size)}, + {"target_path", partition.target_path}, + {"target_hash", + base::HexEncode(partition.target_hash.data(), + partition.target_hash.size())}, + {"run_postinstall", utils::ToString(partition.run_postinstall)}, + {"postinstall_path", partition.postinstall_path}, + {"readonly_target_path", partition.readonly_target_path}, + {"filesystem_type", partition.filesystem_type}, + }, + "\n ")); + } + + for (unsigned int i = 0; i < payloads.size(); ++i) { + const auto& payload = payloads[i]; + result_str.emplace_back(VectorToString( + { + {"Payload", base::NumberToString(i)}, + {"urls", PayloadUrlsToString(payload.payload_urls)}, + {"size", base::NumberToString(payload.size)}, + {"metadata_size", base::NumberToString(payload.metadata_size)}, + {"metadata_signature", payload.metadata_signature}, + {"hash", base::HexEncode(payload.hash.data(), payload.hash.size())}, + {"type", InstallPayloadTypeToString(payload.type)}, + {"fingerprint", payload.fp}, + {"app_id", payload.app_id}, + {"already_applied", utils::ToString(payload.already_applied)}, + }, + "\n ")); + } + + return base::JoinString(result_str, "\n"); } bool InstallPlan::LoadPartitionsFromSlots(BootControlInterface* boot_control) { @@ -108,18 +154,19 @@ bool InstallPlan::LoadPartitionsFromSlots(BootControlInterface* boot_control) { for (Partition& partition : partitions) { if (source_slot != BootControlInterface::kInvalidSlot && partition.source_size > 0) { - result = boot_control->GetPartitionDevice( - partition.name, source_slot, &partition.source_path) && - result; + TEST_AND_RETURN_FALSE(boot_control->GetPartitionDevice( + partition.name, source_slot, &partition.source_path)); } else { partition.source_path.clear(); } if (target_slot != BootControlInterface::kInvalidSlot && partition.target_size > 0) { - result = boot_control->GetPartitionDevice( - partition.name, target_slot, &partition.target_path) && - result; + auto device = boot_control->GetPartitionDevice( + partition.name, target_slot, source_slot); + TEST_AND_RETURN_FALSE(device.has_value()); + partition.target_path = device->rw_device_path; + partition.readonly_target_path = device->readonly_device_path; } else { partition.target_path.clear(); } diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h index ede36b31..7c77789b 100644 --- a/payload_consumer/install_plan.h +++ b/payload_consumer/install_plan.h @@ -45,6 +45,7 @@ struct InstallPlan { bool operator!=(const InstallPlan& that) const; void Dump() const; + std::string ToString() const; // Loads the |source_path| and |target_path| of all |partitions| based on the // |source_slot| and |target_slot| if available. Returns whether it succeeded @@ -54,25 +55,28 @@ struct InstallPlan { bool is_resume{false}; std::string download_url; // url to download from std::string version; // version we are installing. - // system version, if present and separate from version - std::string system_version; struct Payload { - uint64_t size = 0; // size of the payload - uint64_t metadata_size = 0; // size of the metadata + std::vector<std::string> payload_urls; // URLs to download the payload + uint64_t size = 0; // size of the payload + uint64_t metadata_size = 0; // size of the metadata std::string metadata_signature; // signature of the metadata in base64 brillo::Blob hash; // SHA256 hash of the payload InstallPayloadType type{InstallPayloadType::kUnknown}; + std::string fp; // fingerprint value unique to the payload + std::string app_id; // App ID of the payload // Only download manifest and fill in partitions in install plan without // apply the payload if true. Will be set by DownloadAction when resuming // multi-payload. bool already_applied = false; bool operator==(const Payload& that) const { - return size == that.size && metadata_size == that.metadata_size && + return payload_urls == that.payload_urls && size == that.size && + metadata_size == that.metadata_size && metadata_signature == that.metadata_signature && hash == that.hash && type == that.type && - already_applied == that.already_applied; + already_applied == that.already_applied && fp == that.fp && + app_id == that.app_id; } }; std::vector<Payload> payloads; @@ -98,9 +102,17 @@ struct InstallPlan { uint64_t source_size{0}; brillo::Blob source_hash; + // |target_path| is intended to be a path to block device, which you can + // open with |open| syscall and perform regular unix style read/write. + // For VABC, this will be empty. As you can't read/write VABC devices with + // regular syscall. std::string target_path; + // |mountable_target_device| is intended to be a path to block device which + // can be used for mounting this block device's underlying filesystem. + std::string readonly_target_path; uint64_t target_size{0}; brillo::Blob target_hash; + uint32_t block_size{0}; // Whether we should run the postinstall script from this partition and the @@ -146,6 +158,9 @@ struct InstallPlan { // True if this update is a rollback. bool is_rollback{false}; + // True if this rollback should preserve some system data. + bool rollback_data_save_requested{false}; + // True if the update should write verity. // False otherwise. bool write_verity{true}; @@ -153,6 +168,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; @@ -190,9 +209,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/install_plan_unittest.cc b/payload_consumer/install_plan_unittest.cc new file mode 100644 index 00000000..77794949 --- /dev/null +++ b/payload_consumer/install_plan_unittest.cc @@ -0,0 +1,84 @@ +// +// 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/payload_consumer/install_plan.h" + +namespace chromeos_update_engine { + +TEST(InstallPlanTest, Dump) { + InstallPlan install_plan{ + .download_url = "foo-download-url", + .version = "foo-version", + .payloads = {{ + .payload_urls = {"url1", "url2"}, + .metadata_signature = "foo-signature", + .hash = {0xb2, 0xb3}, + .fp = "foo-fp", + .app_id = "foo-app-id", + }}, + .source_slot = BootControlInterface::kInvalidSlot, + .target_slot = BootControlInterface::kInvalidSlot, + .partitions = {{ + .name = "foo-partition_name", + .source_path = "foo-source-path", + .source_hash = {0xb1, 0xb2}, + .target_path = "foo-target-path", + .readonly_target_path = "mountable-device", + .target_hash = {0xb3, 0xb4}, + .postinstall_path = "foo-path", + .filesystem_type = "foo-type", + }}, + }; + + EXPECT_EQ(install_plan.ToString(), + R"(type: new_update +version: foo-version +source_slot: INVALID +target_slot: INVALID +initial url: foo-download-url +hash_checks_mandatory: false +powerwash_required: false +switch_slot_on_reboot: true +run_post_install: true +is_rollback: false +rollback_data_save_requested: false +write_verity: true +Partition: foo-partition_name + source_size: 0 + source_path: foo-source-path + source_hash: B1B2 + target_size: 0 + target_path: foo-target-path + target_hash: B3B4 + run_postinstall: false + postinstall_path: foo-path + readonly_target_path: mountable-device + filesystem_type: foo-type +Payload: 0 + urls: (url1,url2) + size: 0 + metadata_size: 0 + metadata_signature: foo-signature + hash: B2B3 + type: unknown + fingerprint: foo-fp + app_id: foo-app-id + already_applied: false)"); +} + +} // namespace chromeos_update_engine diff --git a/mock_file_writer.h b/payload_consumer/mock_file_writer.h index 26cd45d0..26cd45d0 100644 --- a/mock_file_writer.h +++ b/payload_consumer/mock_file_writer.h diff --git a/payload_consumer/mock_partition_writer.h b/payload_consumer/mock_partition_writer.h new file mode 100644 index 00000000..b056010d --- /dev/null +++ b/payload_consumer/mock_partition_writer.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_MOCK_PARTITION_WRITER_H_ +#define UPDATE_ENGINE_MOCK_PARTITION_WRITER_H_ + +#include <gmock/gmock.h> + +#include "common/error_code.h" +#include "payload_generator/delta_diff_generator.h" +#include "update_engine/payload_consumer/partition_writer.h" + +namespace chromeos_update_engine { +class MockPartitionWriter : public PartitionWriter { + public: + MockPartitionWriter() : PartitionWriter({}, {}, nullptr, kBlockSize, false) {} + virtual ~MockPartitionWriter() = default; + + // Perform necessary initialization work before InstallOperation can be + // applied to this partition + MOCK_METHOD(bool, Init, (const InstallPlan*, bool, size_t), (override)); + + // |CheckpointUpdateProgress| will be called after SetNextOpIndex(), but it's + // optional. DeltaPerformer may or may not call this everytime an operation is + // applied. + MOCK_METHOD(void, CheckpointUpdateProgress, (size_t), (override)); + + // These perform a specific type of operation and return true on success. + // |error| will be set if source hash mismatch, otherwise |error| might not be + // set even if it fails. + MOCK_METHOD(bool, + PerformReplaceOperation, + (const InstallOperation&, const void*, size_t), + (override)); + MOCK_METHOD(bool, + PerformZeroOrDiscardOperation, + (const InstallOperation&), + (override)); + MOCK_METHOD(bool, + PerformSourceCopyOperation, + (const InstallOperation&, ErrorCode*), + (override)); + MOCK_METHOD(bool, + PerformSourceBsdiffOperation, + (const InstallOperation&, ErrorCode*, const void*, size_t), + (override)); + MOCK_METHOD(bool, + PerformPuffDiffOperation, + (const InstallOperation&, ErrorCode*, const void*, size_t), + (override)); +}; + +} // namespace chromeos_update_engine + +#endif diff --git a/payload_consumer/mount_history.cc b/payload_consumer/mount_history.cc index 43a75b34..d699ad92 100644 --- a/payload_consumer/mount_history.cc +++ b/payload_consumer/mount_history.cc @@ -37,7 +37,7 @@ void LogMountHistory(const FileDescriptorPtr blockdevice_fd) { brillo::Blob block0_buffer(kBlockSize); ssize_t bytes_read; - if (!utils::PReadAll( + if (!utils::ReadAll( blockdevice_fd, block0_buffer.data(), kBlockSize, 0, &bytes_read)) { LOG(WARNING) << "PReadAll failed"; return; @@ -54,14 +54,33 @@ void LogMountHistory(const FileDescriptorPtr blockdevice_fd) { // 0x30: len32 Write time // 0x34: len16 Number of mounts since the last fsck // 0x38: len16 Magic signature 0xEF53 + // 0x40: len32 Time of last check + // 0x108: len32 When the filesystem was created time_t mount_time = *reinterpret_cast<uint32_t*>(&block0_buffer[0x400 + 0x2C]); + time_t write_time = + *reinterpret_cast<uint32_t*>(&block0_buffer[0x400 + 0x30]); uint16_t mount_count = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x34]); uint16_t magic = *reinterpret_cast<uint16_t*>(&block0_buffer[0x400 + 0x38]); + time_t check_time = + *reinterpret_cast<uint32_t*>(&block0_buffer[0x400 + 0x40]); + time_t created_time = + *reinterpret_cast<uint32_t*>(&block0_buffer[0x400 + 0x108]); if (magic == 0xEF53) { + // Timestamps can be updated by fsck without updating mount count, + // log if any timestamp differ + if (! (write_time == created_time && check_time == created_time)) { + LOG(WARNING) << "Device have been modified after being created. " + << "Filesystem created on " + << base::Time::FromTimeT(created_time) << ", " + << "last written on " + << base::Time::FromTimeT(write_time) << ", " + << "last checked on " + << base::Time::FromTimeT(check_time) << "."; + } if (mount_count > 0) { LOG(WARNING) << "Device was remounted R/W " << mount_count << " times. " << "Last remount happened on " diff --git a/payload_consumer/mtd_file_descriptor.cc b/payload_consumer/mtd_file_descriptor.cc deleted file mode 100644 index 5d940cbb..00000000 --- a/payload_consumer/mtd_file_descriptor.cc +++ /dev/null @@ -1,263 +0,0 @@ -// -// Copyright (C) 2014 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include "update_engine/payload_consumer/mtd_file_descriptor.h" - -#include <fcntl.h> -#include <mtd/ubi-user.h> -#include <sys/ioctl.h> -#include <sys/stat.h> -#include <sys/types.h> - -#include <memory> -#include <string> - -#include <base/files/file_path.h> -#include <base/strings/string_number_conversions.h> -#include <base/strings/string_util.h> -#include <base/strings/stringprintf.h> - -#include "update_engine/common/subprocess.h" -#include "update_engine/common/utils.h" - -using std::string; - -namespace { - -static const char kSysfsClassUbi[] = "/sys/class/ubi/"; -static const char kUsableEbSize[] = "/usable_eb_size"; -static const char kReservedEbs[] = "/reserved_ebs"; - -using chromeos_update_engine::UbiVolumeInfo; -using chromeos_update_engine::utils::ReadFile; - -// Return a UbiVolumeInfo pointer if |path| is a UBI volume. Otherwise, return -// a null unique pointer. -std::unique_ptr<UbiVolumeInfo> GetUbiVolumeInfo(const string& path) { - base::FilePath device_node(path); - base::FilePath ubi_name(device_node.BaseName()); - - string sysfs_node(kSysfsClassUbi); - sysfs_node.append(ubi_name.MaybeAsASCII()); - - std::unique_ptr<UbiVolumeInfo> ret; - - // Obtain volume info from sysfs. - string s_reserved_ebs; - if (!ReadFile(sysfs_node + kReservedEbs, &s_reserved_ebs)) { - LOG(ERROR) << "Cannot read " << sysfs_node + kReservedEbs; - return ret; - } - string s_eb_size; - if (!ReadFile(sysfs_node + kUsableEbSize, &s_eb_size)) { - LOG(ERROR) << "Cannot read " << sysfs_node + kUsableEbSize; - return ret; - } - - base::TrimWhitespaceASCII( - s_reserved_ebs, base::TRIM_TRAILING, &s_reserved_ebs); - base::TrimWhitespaceASCII(s_eb_size, base::TRIM_TRAILING, &s_eb_size); - - uint64_t reserved_ebs, eb_size; - if (!base::StringToUint64(s_reserved_ebs, &reserved_ebs)) { - LOG(ERROR) << "Cannot parse reserved_ebs: " << s_reserved_ebs; - return ret; - } - if (!base::StringToUint64(s_eb_size, &eb_size)) { - LOG(ERROR) << "Cannot parse usable_eb_size: " << s_eb_size; - return ret; - } - - ret.reset(new UbiVolumeInfo); - ret->reserved_ebs = reserved_ebs; - ret->eraseblock_size = eb_size; - return ret; -} - -} // namespace - -namespace chromeos_update_engine { - -MtdFileDescriptor::MtdFileDescriptor() - : read_ctx_(nullptr, &mtd_read_close), - write_ctx_(nullptr, &mtd_write_close) {} - -bool MtdFileDescriptor::IsMtd(const char* path) { - uint64_t size; - return mtd_node_info(path, &size, nullptr, nullptr) == 0; -} - -bool MtdFileDescriptor::Open(const char* path, int flags, mode_t mode) { - // This File Descriptor does not support read and write. - TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR); - // But we need to open the underlying file descriptor in O_RDWR mode because - // during write, we need to read back to verify the write actually sticks or - // we have to skip the block. That job is done by mtdutils library. - if ((flags & O_ACCMODE) == O_WRONLY) { - flags &= ~O_ACCMODE; - flags |= O_RDWR; - } - TEST_AND_RETURN_FALSE( - EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode)); - - if ((flags & O_ACCMODE) == O_RDWR) { - write_ctx_.reset(mtd_write_descriptor(fd_, path)); - nr_written_ = 0; - } else { - read_ctx_.reset(mtd_read_descriptor(fd_, path)); - } - - if (!read_ctx_ && !write_ctx_) { - Close(); - return false; - } - - return true; -} - -bool MtdFileDescriptor::Open(const char* path, int flags) { - mode_t cur = umask(022); - umask(cur); - return Open(path, flags, 0777 & ~cur); -} - -ssize_t MtdFileDescriptor::Read(void* buf, size_t count) { - CHECK(read_ctx_); - return mtd_read_data(read_ctx_.get(), static_cast<char*>(buf), count); -} - -ssize_t MtdFileDescriptor::Write(const void* buf, size_t count) { - CHECK(write_ctx_); - ssize_t result = - mtd_write_data(write_ctx_.get(), static_cast<const char*>(buf), count); - if (result > 0) { - nr_written_ += result; - } - return result; -} - -off64_t MtdFileDescriptor::Seek(off64_t offset, int whence) { - if (write_ctx_) { - // Ignore seek in write mode. - return nr_written_; - } - return EintrSafeFileDescriptor::Seek(offset, whence); -} - -bool MtdFileDescriptor::Close() { - read_ctx_.reset(); - write_ctx_.reset(); - return EintrSafeFileDescriptor::Close(); -} - -bool UbiFileDescriptor::IsUbi(const char* path) { - base::FilePath device_node(path); - base::FilePath ubi_name(device_node.BaseName()); - TEST_AND_RETURN_FALSE(base::StartsWith( - ubi_name.MaybeAsASCII(), "ubi", base::CompareCase::SENSITIVE)); - - return static_cast<bool>(GetUbiVolumeInfo(path)); -} - -bool UbiFileDescriptor::Open(const char* path, int flags, mode_t mode) { - std::unique_ptr<UbiVolumeInfo> info = GetUbiVolumeInfo(path); - if (!info) { - return false; - } - - // This File Descriptor does not support read and write. - TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR); - TEST_AND_RETURN_FALSE( - EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode)); - - usable_eb_blocks_ = info->reserved_ebs; - eraseblock_size_ = info->eraseblock_size; - volume_size_ = usable_eb_blocks_ * eraseblock_size_; - - if ((flags & O_ACCMODE) == O_WRONLY) { - // It's best to use volume update ioctl so that UBI layer will mark the - // volume as being updated, and only clear that mark if the update is - // successful. We will need to pad to the whole volume size at close. - uint64_t vsize = volume_size_; - if (ioctl(fd_, UBI_IOCVOLUP, &vsize) != 0) { - PLOG(ERROR) << "Cannot issue volume update ioctl"; - EintrSafeFileDescriptor::Close(); - return false; - } - mode_ = kWriteOnly; - nr_written_ = 0; - } else { - mode_ = kReadOnly; - } - - return true; -} - -bool UbiFileDescriptor::Open(const char* path, int flags) { - mode_t cur = umask(022); - umask(cur); - return Open(path, flags, 0777 & ~cur); -} - -ssize_t UbiFileDescriptor::Read(void* buf, size_t count) { - CHECK(mode_ == kReadOnly); - return EintrSafeFileDescriptor::Read(buf, count); -} - -ssize_t UbiFileDescriptor::Write(const void* buf, size_t count) { - CHECK(mode_ == kWriteOnly); - ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, count); - if (nr_chunk >= 0) { - nr_written_ += nr_chunk; - } - return nr_chunk; -} - -off64_t UbiFileDescriptor::Seek(off64_t offset, int whence) { - if (mode_ == kWriteOnly) { - // Ignore seek in write mode. - return nr_written_; - } - return EintrSafeFileDescriptor::Seek(offset, whence); -} - -bool UbiFileDescriptor::Close() { - bool pad_ok = true; - if (IsOpen() && mode_ == kWriteOnly) { - char buf[1024]; - memset(buf, 0xFF, sizeof(buf)); - while (nr_written_ < volume_size_) { - // We have written less than the whole volume. In order for us to clear - // the update marker, we need to fill the rest. It is recommended to fill - // UBI writes with 0xFF. - uint64_t to_write = volume_size_ - nr_written_; - if (to_write > sizeof(buf)) { - to_write = sizeof(buf); - } - ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, to_write); - if (nr_chunk < 0) { - LOG(ERROR) << "Cannot 0xFF-pad before closing."; - // There is an error, but we can't really do any meaningful thing here. - pad_ok = false; - break; - } - nr_written_ += nr_chunk; - } - } - return EintrSafeFileDescriptor::Close() && pad_ok; -} - -} // namespace chromeos_update_engine diff --git a/payload_consumer/mtd_file_descriptor.h b/payload_consumer/mtd_file_descriptor.h deleted file mode 100644 index c0170b78..00000000 --- a/payload_consumer/mtd_file_descriptor.h +++ /dev/null @@ -1,103 +0,0 @@ -// -// Copyright (C) 2014 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_MTD_FILE_DESCRIPTOR_H_ -#define UPDATE_ENGINE_PAYLOAD_CONSUMER_MTD_FILE_DESCRIPTOR_H_ - -// This module defines file descriptors that deal with NAND media. We are -// concerned with raw NAND access (as MTD device), and through UBI layer. - -#include <memory> - -#include <mtdutils.h> - -#include "update_engine/payload_consumer/file_descriptor.h" - -namespace chromeos_update_engine { - -// A class defining the file descriptor API for raw MTD device. This file -// descriptor supports either random read, or sequential write but not both at -// once. -class MtdFileDescriptor : public EintrSafeFileDescriptor { - public: - MtdFileDescriptor(); - - static bool IsMtd(const char* path); - - bool Open(const char* path, int flags, mode_t mode) override; - bool Open(const char* path, int flags) override; - ssize_t Read(void* buf, size_t count) override; - ssize_t Write(const void* buf, size_t count) override; - off64_t Seek(off64_t offset, int whence) override; - uint64_t BlockDevSize() override { return 0; } - bool BlkIoctl(int request, - uint64_t start, - uint64_t length, - int* result) override { - return false; - } - bool Close() override; - - private: - std::unique_ptr<MtdReadContext, decltype(&mtd_read_close)> read_ctx_; - std::unique_ptr<MtdWriteContext, decltype(&mtd_write_close)> write_ctx_; - uint64_t nr_written_; -}; - -struct UbiVolumeInfo { - // Number of eraseblocks. - uint64_t reserved_ebs; - // Size of each eraseblock. - uint64_t eraseblock_size; -}; - -// A file descriptor to update a UBI volume, similar to MtdFileDescriptor. -// Once the file descriptor is opened for write, the volume is marked as being -// updated. The volume will not be usable until an update is completed. See -// UBI_IOCVOLUP ioctl operation. -class UbiFileDescriptor : public EintrSafeFileDescriptor { - public: - // Perform some queries about |path| to see if it is a UBI volume. - static bool IsUbi(const char* path); - - bool Open(const char* path, int flags, mode_t mode) override; - bool Open(const char* path, int flags) override; - ssize_t Read(void* buf, size_t count) override; - ssize_t Write(const void* buf, size_t count) override; - off64_t Seek(off64_t offset, int whence) override; - uint64_t BlockDevSize() override { return 0; } - bool BlkIoctl(int request, - uint64_t start, - uint64_t length, - int* result) override { - return false; - } - bool Close() override; - - private: - enum Mode { kReadOnly, kWriteOnly }; - - uint64_t usable_eb_blocks_; - uint64_t eraseblock_size_; - uint64_t volume_size_; - uint64_t nr_written_; - - Mode mode_; -}; - -} // namespace chromeos_update_engine - -#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_MTD_FILE_DESCRIPTOR_H_ diff --git a/payload_consumer/partition_update_generator_android.cc b/payload_consumer/partition_update_generator_android.cc new file mode 100644 index 00000000..44671828 --- /dev/null +++ b/payload_consumer/partition_update_generator_android.cc @@ -0,0 +1,183 @@ +// +// 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) { +#ifndef __ANDROID__ + // Skip copying partitions for host verification. + return true; +#endif + + 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/partition_writer.cc b/payload_consumer/partition_writer.cc new file mode 100644 index 00000000..6f98ba36 --- /dev/null +++ b/payload_consumer/partition_writer.cc @@ -0,0 +1,661 @@ +// +// 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_writer.h> + +#include <fcntl.h> +#include <linux/fs.h> + +#include <algorithm> +#include <initializer_list> +#include <memory> +#include <utility> +#include <vector> + +#include <base/strings/string_number_conversions.h> +#include <bsdiff/bspatch.h> +#include <puffin/puffpatch.h> +#include <bsdiff/file_interface.h> +#include <puffin/stream.h> + +#include "update_engine/common/terminator.h" +#include "update_engine/common/utils.h" +#include "update_engine/payload_consumer/bzip_extent_writer.h" +#include "update_engine/payload_consumer/cached_file_descriptor.h" +#include "update_engine/payload_consumer/extent_reader.h" +#include "update_engine/payload_consumer/extent_writer.h" +#include "update_engine/payload_consumer/fec_file_descriptor.h" +#include "update_engine/payload_consumer/file_descriptor_utils.h" +#include "update_engine/payload_consumer/install_plan.h" +#include "update_engine/payload_consumer/mount_history.h" +#include "update_engine/payload_consumer/payload_constants.h" +#include "update_engine/payload_consumer/xz_extent_writer.h" + +namespace chromeos_update_engine { + +namespace { +constexpr uint64_t kCacheSize = 1024 * 1024; // 1MB + +// Discard the tail of the block device referenced by |fd|, from the offset +// |data_size| until the end of the block device. Returns whether the data was +// discarded. + +bool DiscardPartitionTail(const FileDescriptorPtr& fd, uint64_t data_size) { + uint64_t part_size = fd->BlockDevSize(); + if (!part_size || part_size <= data_size) + return false; + + struct blkioctl_request { + int number; + const char* name; + }; + const std::initializer_list<blkioctl_request> blkioctl_requests = { + {BLKDISCARD, "BLKDISCARD"}, + {BLKSECDISCARD, "BLKSECDISCARD"}, +#ifdef BLKZEROOUT + {BLKZEROOUT, "BLKZEROOUT"}, +#endif + }; + for (const auto& req : blkioctl_requests) { + int error = 0; + if (fd->BlkIoctl(req.number, data_size, part_size - data_size, &error) && + error == 0) { + return true; + } + LOG(WARNING) << "Error discarding the last " + << (part_size - data_size) / 1024 << " KiB using ioctl(" + << req.name << ")"; + } + return false; +} + +} // namespace + +// Opens path for read/write. On success returns an open FileDescriptor +// and sets *err to 0. On failure, sets *err to errno and returns nullptr. +FileDescriptorPtr OpenFile(const char* path, + int mode, + bool cache_writes, + int* err) { + // Try to mark the block device read-only based on the mode. Ignore any + // failure since this won't work when passing regular files. + bool read_only = (mode & O_ACCMODE) == O_RDONLY; + utils::SetBlockDeviceReadOnly(path, read_only); + + FileDescriptorPtr fd(new EintrSafeFileDescriptor()); + if (cache_writes && !read_only) { + fd = FileDescriptorPtr(new CachedFileDescriptor(fd, kCacheSize)); + LOG(INFO) << "Caching writes."; + } + if (!fd->Open(path, mode, 000)) { + *err = errno; + PLOG(ERROR) << "Unable to open file " << path; + return nullptr; + } + *err = 0; + return fd; +} + +class BsdiffExtentFile : public bsdiff::FileInterface { + public: + BsdiffExtentFile(std::unique_ptr<ExtentReader> reader, size_t size) + : BsdiffExtentFile(std::move(reader), nullptr, size) {} + BsdiffExtentFile(std::unique_ptr<ExtentWriter> writer, size_t size) + : BsdiffExtentFile(nullptr, std::move(writer), size) {} + + ~BsdiffExtentFile() override = default; + + bool Read(void* buf, size_t count, size_t* bytes_read) override { + TEST_AND_RETURN_FALSE(reader_->Read(buf, count)); + *bytes_read = count; + offset_ += count; + return true; + } + + bool Write(const void* buf, size_t count, size_t* bytes_written) override { + TEST_AND_RETURN_FALSE(writer_->Write(buf, count)); + *bytes_written = count; + offset_ += count; + return true; + } + + bool Seek(off_t pos) override { + if (reader_ != nullptr) { + TEST_AND_RETURN_FALSE(reader_->Seek(pos)); + offset_ = pos; + } else { + // For writes technically there should be no change of position, or it + // should be equivalent of current offset. + TEST_AND_RETURN_FALSE(offset_ == static_cast<uint64_t>(pos)); + } + return true; + } + + bool Close() override { return true; } + + bool GetSize(uint64_t* size) override { + *size = size_; + return true; + } + + private: + BsdiffExtentFile(std::unique_ptr<ExtentReader> reader, + std::unique_ptr<ExtentWriter> writer, + size_t size) + : reader_(std::move(reader)), + writer_(std::move(writer)), + size_(size), + offset_(0) {} + + std::unique_ptr<ExtentReader> reader_; + std::unique_ptr<ExtentWriter> writer_; + uint64_t size_; + uint64_t offset_; + + DISALLOW_COPY_AND_ASSIGN(BsdiffExtentFile); +}; +// A class to be passed to |puffpatch| for reading from |source_fd_| and writing +// into |target_fd_|. +class PuffinExtentStream : public puffin::StreamInterface { + public: + // Constructor for creating a stream for reading from an |ExtentReader|. + PuffinExtentStream(std::unique_ptr<ExtentReader> reader, uint64_t size) + : PuffinExtentStream(std::move(reader), nullptr, size) {} + + // Constructor for creating a stream for writing to an |ExtentWriter|. + PuffinExtentStream(std::unique_ptr<ExtentWriter> writer, uint64_t size) + : PuffinExtentStream(nullptr, std::move(writer), size) {} + + ~PuffinExtentStream() override = default; + + bool GetSize(uint64_t* size) const override { + *size = size_; + return true; + } + + bool GetOffset(uint64_t* offset) const override { + *offset = offset_; + return true; + } + + bool Seek(uint64_t offset) override { + if (is_read_) { + TEST_AND_RETURN_FALSE(reader_->Seek(offset)); + offset_ = offset; + } else { + // For writes technically there should be no change of position, or it + // should equivalent of current offset. + TEST_AND_RETURN_FALSE(offset_ == offset); + } + return true; + } + + bool Read(void* buffer, size_t count) override { + TEST_AND_RETURN_FALSE(is_read_); + TEST_AND_RETURN_FALSE(reader_->Read(buffer, count)); + offset_ += count; + return true; + } + + bool Write(const void* buffer, size_t count) override { + TEST_AND_RETURN_FALSE(!is_read_); + TEST_AND_RETURN_FALSE(writer_->Write(buffer, count)); + offset_ += count; + return true; + } + + bool Close() override { return true; } + + private: + PuffinExtentStream(std::unique_ptr<ExtentReader> reader, + std::unique_ptr<ExtentWriter> writer, + uint64_t size) + : reader_(std::move(reader)), + writer_(std::move(writer)), + size_(size), + offset_(0), + is_read_(reader_ ? true : false) {} + + std::unique_ptr<ExtentReader> reader_; + std::unique_ptr<ExtentWriter> writer_; + uint64_t size_; + uint64_t offset_; + bool is_read_; + + DISALLOW_COPY_AND_ASSIGN(PuffinExtentStream); +}; + +PartitionWriter::PartitionWriter( + const PartitionUpdate& partition_update, + const InstallPlan::Partition& install_part, + DynamicPartitionControlInterface* dynamic_control, + size_t block_size, + bool is_interactive) + : partition_update_(partition_update), + install_part_(install_part), + dynamic_control_(dynamic_control), + interactive_(is_interactive), + block_size_(block_size) {} + +PartitionWriter::~PartitionWriter() { + Close(); +} + +bool PartitionWriter::OpenSourcePartition(uint32_t source_slot, + bool source_may_exist) { + source_path_.clear(); + if (!source_may_exist) { + return true; + } + if (install_part_.source_size > 0 && !install_part_.source_path.empty()) { + source_path_ = install_part_.source_path; + int err; + source_fd_ = OpenFile(source_path_.c_str(), O_RDONLY, false, &err); + if (source_fd_ == nullptr) { + LOG(ERROR) << "Unable to open source partition " << install_part_.name + << " on slot " << BootControlInterface::SlotName(source_slot) + << ", file " << source_path_; + return false; + } + } + return true; +} + +bool PartitionWriter::Init(const InstallPlan* install_plan, + bool source_may_exist, + size_t next_op_index) { + const PartitionUpdate& partition = partition_update_; + uint32_t source_slot = install_plan->source_slot; + uint32_t target_slot = install_plan->target_slot; + TEST_AND_RETURN_FALSE(OpenSourcePartition(source_slot, source_may_exist)); + + // 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. + + target_path_ = install_part_.target_path; + int err; + + int flags = O_RDWR; + if (!interactive_) + flags |= O_DSYNC; + + LOG(INFO) << "Opening " << target_path_ << " partition with" + << (interactive_ ? "out" : "") << " O_DSYNC"; + + target_fd_ = OpenFile(target_path_.c_str(), flags, true, &err); + if (!target_fd_) { + LOG(ERROR) << "Unable to open target partition " + << partition.partition_name() << " on slot " + << BootControlInterface::SlotName(target_slot) << ", file " + << target_path_; + return false; + } + + LOG(INFO) << "Applying " << partition.operations().size() + << " operations to partition \"" << partition.partition_name() + << "\""; + + // Discard the end of the partition, but ignore failures. + DiscardPartitionTail(target_fd_, install_part_.target_size); + + return true; +} + +bool PartitionWriter::PerformReplaceOperation(const InstallOperation& operation, + const void* data, + size_t count) { + // Setup the ExtentWriter stack based on the operation type. + std::unique_ptr<ExtentWriter> writer = CreateBaseExtentWriter(); + + if (operation.type() == InstallOperation::REPLACE_BZ) { + writer.reset(new BzipExtentWriter(std::move(writer))); + } else if (operation.type() == InstallOperation::REPLACE_XZ) { + writer.reset(new XzExtentWriter(std::move(writer))); + } + + TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_)); + TEST_AND_RETURN_FALSE(writer->Write(data, operation.data_length())); + + return true; +} + +bool PartitionWriter::PerformZeroOrDiscardOperation( + const InstallOperation& operation) { +#ifdef BLKZEROOUT + bool attempt_ioctl = true; + int request = + (operation.type() == InstallOperation::ZERO ? BLKZEROOUT : BLKDISCARD); +#else // !defined(BLKZEROOUT) + bool attempt_ioctl = false; + int request = 0; +#endif // !defined(BLKZEROOUT) + + brillo::Blob zeros; + for (const Extent& extent : operation.dst_extents()) { + const uint64_t start = extent.start_block() * block_size_; + const uint64_t length = extent.num_blocks() * block_size_; + if (attempt_ioctl) { + int result = 0; + if (target_fd_->BlkIoctl(request, start, length, &result) && result == 0) + continue; + attempt_ioctl = false; + } + // In case of failure, we fall back to writing 0 to the selected region. + zeros.resize(16 * block_size_); + for (uint64_t offset = 0; offset < length; offset += zeros.size()) { + uint64_t chunk_length = + std::min(length - offset, static_cast<uint64_t>(zeros.size())); + TEST_AND_RETURN_FALSE(utils::WriteAll( + target_fd_, zeros.data(), chunk_length, start + offset)); + } + } + return true; +} + +bool PartitionWriter::PerformSourceCopyOperation( + const InstallOperation& operation, ErrorCode* error) { + 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 = partition_update_; + const auto& partition_control = dynamic_control_; + + InstallOperation buf; + const 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()); + + // We fall back to use the error corrected device if the hash of the raw + // device doesn't match or there was an error reading the source partition. + // Note that this code will also fall back if writing the target partition + // fails. + if (should_optimize) { + // Hash operation.src_extents(), then copy optimized.src_extents to + // optimized.dst_extents. + read_ok = + fd_utils::ReadAndHashExtents( + source_fd_, operation.src_extents(), block_size_, &source_hash) && + fd_utils::CopyAndHashExtents(source_fd_, + optimized.src_extents(), + target_fd_, + optimized.dst_extents(), + block_size_, + nullptr /* skip hashing */); + } else { + read_ok = fd_utils::CopyAndHashExtents(source_fd_, + operation.src_extents(), + target_fd_, + operation.dst_extents(), + block_size_, + &source_hash); + } + if (read_ok && expected_source_hash == source_hash) + return true; + LOG(WARNING) << "Source hash from RAW device mismatched, attempting to " + "correct using ECC"; + if (!OpenCurrentECCPartition()) { + // The following function call will return false since the source hash + // mismatches, but we still want to call it so it prints the appropriate + // log message. + return ValidateSourceHash(source_hash, operation, source_fd_, error); + } + + LOG(WARNING) << "Source hash from RAW device mismatched: found " + << base::HexEncode(source_hash.data(), source_hash.size()) + << ", expected " + << base::HexEncode(expected_source_hash.data(), + expected_source_hash.size()); + if (should_optimize) { + TEST_AND_RETURN_FALSE(fd_utils::ReadAndHashExtents( + source_ecc_fd_, operation.src_extents(), block_size_, &source_hash)); + TEST_AND_RETURN_FALSE( + fd_utils::CopyAndHashExtents(source_ecc_fd_, + optimized.src_extents(), + target_fd_, + optimized.dst_extents(), + block_size_, + nullptr /* skip hashing */)); + } else { + TEST_AND_RETURN_FALSE( + fd_utils::CopyAndHashExtents(source_ecc_fd_, + operation.src_extents(), + target_fd_, + operation.dst_extents(), + block_size_, + &source_hash)); + } + TEST_AND_RETURN_FALSE( + ValidateSourceHash(source_hash, operation, source_ecc_fd_, error)); + // At this point reading from the error corrected device worked, but + // reading from the raw device failed, so this is considered a recovered + // failure. + source_ecc_recovered_failures_++; + } else { + // When the operation doesn't include a source hash, we attempt the error + // corrected device first since we can't verify the block in the raw device + // at this point, but we fall back to the raw device since the error + // corrected device can be shorter or not available. + + if (OpenCurrentECCPartition() && + fd_utils::CopyAndHashExtents(source_ecc_fd_, + optimized.src_extents(), + target_fd_, + optimized.dst_extents(), + block_size_, + nullptr)) { + return true; + } + TEST_AND_RETURN_FALSE(fd_utils::CopyAndHashExtents(source_fd_, + optimized.src_extents(), + target_fd_, + optimized.dst_extents(), + block_size_, + nullptr)); + } + return true; +} + +bool PartitionWriter::PerformSourceBsdiffOperation( + const InstallOperation& operation, + ErrorCode* error, + const void* data, + size_t count) { + FileDescriptorPtr source_fd = ChooseSourceFD(operation, error); + TEST_AND_RETURN_FALSE(source_fd != nullptr); + + auto reader = std::make_unique<DirectExtentReader>(); + TEST_AND_RETURN_FALSE( + reader->Init(source_fd, operation.src_extents(), block_size_)); + auto src_file = std::make_unique<BsdiffExtentFile>( + std::move(reader), + utils::BlocksInExtents(operation.src_extents()) * block_size_); + + auto writer = CreateBaseExtentWriter(); + TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_)); + auto dst_file = std::make_unique<BsdiffExtentFile>( + std::move(writer), + utils::BlocksInExtents(operation.dst_extents()) * block_size_); + + TEST_AND_RETURN_FALSE(bsdiff::bspatch(std::move(src_file), + std::move(dst_file), + reinterpret_cast<const uint8_t*>(data), + count) == 0); + return true; +} + +bool PartitionWriter::PerformPuffDiffOperation( + const InstallOperation& operation, + ErrorCode* error, + const void* data, + size_t count) { + FileDescriptorPtr source_fd = ChooseSourceFD(operation, error); + TEST_AND_RETURN_FALSE(source_fd != nullptr); + + auto reader = std::make_unique<DirectExtentReader>(); + TEST_AND_RETURN_FALSE( + reader->Init(source_fd, operation.src_extents(), block_size_)); + puffin::UniqueStreamPtr src_stream(new PuffinExtentStream( + std::move(reader), + utils::BlocksInExtents(operation.src_extents()) * block_size_)); + + auto writer = CreateBaseExtentWriter(); + TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_)); + puffin::UniqueStreamPtr dst_stream(new PuffinExtentStream( + std::move(writer), + utils::BlocksInExtents(operation.dst_extents()) * block_size_)); + + constexpr size_t kMaxCacheSize = 5 * 1024 * 1024; // Total 5MB cache. + TEST_AND_RETURN_FALSE( + puffin::PuffPatch(std::move(src_stream), + std::move(dst_stream), + reinterpret_cast<const uint8_t*>(data), + count, + kMaxCacheSize)); + return true; +} + +FileDescriptorPtr PartitionWriter::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 + // at this point, but we first need to make sure all extents are readable + // since the error corrected device can be shorter or not available. + if (OpenCurrentECCPartition() && + fd_utils::ReadAndHashExtents( + source_ecc_fd_, operation.src_extents(), block_size_, nullptr)) { + return source_ecc_fd_; + } + return source_fd_; + } + + brillo::Blob source_hash; + brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(), + operation.src_sha256_hash().end()); + if (fd_utils::ReadAndHashExtents( + source_fd_, operation.src_extents(), block_size_, &source_hash) && + source_hash == expected_source_hash) { + return source_fd_; + } + // We fall back to use the error corrected device if the hash of the raw + // device doesn't match or there was an error reading the source partition. + if (!OpenCurrentECCPartition()) { + // The following function call will return false since the source hash + // mismatches, but we still want to call it so it prints the appropriate + // log message. + ValidateSourceHash(source_hash, operation, source_fd_, error); + return nullptr; + } + LOG(WARNING) << "Source hash from RAW device mismatched: found " + << base::HexEncode(source_hash.data(), source_hash.size()) + << ", expected " + << base::HexEncode(expected_source_hash.data(), + expected_source_hash.size()); + + if (fd_utils::ReadAndHashExtents( + source_ecc_fd_, operation.src_extents(), block_size_, &source_hash) && + ValidateSourceHash(source_hash, operation, source_ecc_fd_, error)) { + // At this point reading from the error corrected device worked, but + // reading from the raw device failed, so this is considered a recovered + // failure. + source_ecc_recovered_failures_++; + return source_ecc_fd_; + } + return nullptr; +} + +bool PartitionWriter::OpenCurrentECCPartition() { + // No support for ECC for full payloads. + // Full payload should not have any opeartion that requires ECC partitions. + if (source_ecc_fd_) + return true; + + if (source_ecc_open_failure_) + return false; + +#if USE_FEC + const PartitionUpdate& partition = partition_update_; + const InstallPlan::Partition& install_part = install_part_; + std::string path = install_part.source_path; + FileDescriptorPtr fd(new FecFileDescriptor()); + if (!fd->Open(path.c_str(), O_RDONLY, 0)) { + PLOG(ERROR) << "Unable to open ECC source partition " + << partition.partition_name() << ", file " << path; + source_ecc_open_failure_ = true; + return false; + } + source_ecc_fd_ = fd; +#else + // No support for ECC compiled. + source_ecc_open_failure_ = true; +#endif // USE_FEC + + return !source_ecc_open_failure_; +} + +int PartitionWriter::Close() { + int err = 0; + if (source_fd_ && !source_fd_->Close()) { + err = errno; + PLOG(ERROR) << "Error closing source partition"; + if (!err) + err = 1; + } + source_fd_.reset(); + source_path_.clear(); + + if (target_fd_ && !target_fd_->Close()) { + err = errno; + PLOG(ERROR) << "Error closing target partition"; + if (!err) + err = 1; + } + target_fd_.reset(); + target_path_.clear(); + + if (source_ecc_fd_ && !source_ecc_fd_->Close()) { + err = errno; + PLOG(ERROR) << "Error closing ECC source partition"; + if (!err) + err = 1; + } + source_ecc_fd_.reset(); + source_ecc_open_failure_ = false; + return -err; +} + +void PartitionWriter::CheckpointUpdateProgress(size_t next_op_index) { + target_fd_->Flush(); +} + +std::unique_ptr<ExtentWriter> PartitionWriter::CreateBaseExtentWriter() { + return std::make_unique<DirectExtentWriter>(target_fd_); +} + +} // namespace chromeos_update_engine diff --git a/payload_consumer/partition_writer.h b/payload_consumer/partition_writer.h new file mode 100644 index 00000000..82e557a2 --- /dev/null +++ b/payload_consumer/partition_writer.h @@ -0,0 +1,147 @@ +// +// 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_PARTITION_WRITER_H_ +#define UPDATE_ENGINE_PARTITION_WRITER_H_ + +#include <cstdint> +#include <memory> +#include <string> + +#include <brillo/secure_blob.h> +#include <gtest/gtest_prod.h> + +#include "update_engine/common/dynamic_partition_control_interface.h" +#include "update_engine/payload_consumer/extent_writer.h" +#include "update_engine/payload_consumer/file_descriptor.h" +#include "update_engine/payload_consumer/install_plan.h" +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { +class PartitionWriter { + public: + PartitionWriter(const PartitionUpdate& partition_update, + const InstallPlan::Partition& install_part, + DynamicPartitionControlInterface* dynamic_control, + size_t block_size, + bool is_interactive); + virtual ~PartitionWriter(); + static bool ValidateSourceHash(const brillo::Blob& calculated_hash, + const InstallOperation& operation, + const FileDescriptorPtr source_fd, + ErrorCode* error); + + // Perform necessary initialization work before InstallOperation can be + // applied to this partition + [[nodiscard]] virtual bool Init(const InstallPlan* install_plan, + bool source_may_exist, + size_t next_op_index); + + // |CheckpointUpdateProgress| will be called after SetNextOpIndex(), but it's + // optional. DeltaPerformer may or may not call this everytime an operation is + // applied. + // |next_op_index| is index of next operation that should be applied. + // |next_op_index-1| is the last operation that is already applied. + virtual void CheckpointUpdateProgress(size_t next_op_index); + + // Close partition writer, when calling this function there's no guarantee + // that all |InstallOperations| are sent to |PartitionWriter|. This function + // will be called even if we are pausing/aborting the update. + int Close(); + + // These perform a specific type of operation and return true on success. + // |error| will be set if source hash mismatch, otherwise |error| might not be + // set even if it fails. + [[nodiscard]] virtual bool PerformReplaceOperation( + const InstallOperation& operation, const void* data, size_t count); + [[nodiscard]] virtual bool PerformZeroOrDiscardOperation( + const InstallOperation& operation); + + [[nodiscard]] virtual bool PerformSourceCopyOperation( + const InstallOperation& operation, ErrorCode* error); + [[nodiscard]] virtual bool PerformSourceBsdiffOperation( + const InstallOperation& operation, + ErrorCode* error, + const void* data, + size_t count); + [[nodiscard]] virtual bool PerformPuffDiffOperation( + const InstallOperation& operation, + ErrorCode* error, + const void* data, + size_t count); + + // |DeltaPerformer| calls this when all Install Ops are sent to partition + // writer. No |Perform*Operation| methods will be called in the future, and + // the partition writer is expected to be closed soon. + [[nodiscard]] virtual bool FinishedInstallOps() { return true; } + + protected: + friend class PartitionWriterTest; + FRIEND_TEST(PartitionWriterTest, ChooseSourceFDTest); + + bool OpenSourcePartition(uint32_t source_slot, bool source_may_exist); + + bool OpenCurrentECCPartition(); + // For a given operation, choose the source fd to be used (raw device or error + // correction device) based on the source operation hash. + // Returns nullptr if the source hash mismatch cannot be corrected, and set + // the |error| accordingly. + FileDescriptorPtr ChooseSourceFD(const InstallOperation& operation, + ErrorCode* error); + [[nodiscard]] virtual std::unique_ptr<ExtentWriter> CreateBaseExtentWriter(); + + const PartitionUpdate& partition_update_; + const InstallPlan::Partition& install_part_; + DynamicPartitionControlInterface* dynamic_control_; + // Path to source partition + std::string source_path_; + // Path to target partition + std::string target_path_; + FileDescriptorPtr source_fd_; + FileDescriptorPtr target_fd_; + const bool interactive_; + const size_t block_size_; + // File descriptor of the error corrected source partition. Only set while + // updating partition using a delta payload for a partition where error + // correction is available. The size of the error corrected device is smaller + // than the underlying raw device, since it doesn't include the error + // correction blocks. + FileDescriptorPtr source_ecc_fd_{nullptr}; + + // The total number of operations that failed source hash verification but + // passed after falling back to the error-corrected |source_ecc_fd_| device. + uint64_t source_ecc_recovered_failures_{0}; + + // Whether opening the current partition as an error-corrected device failed. + // Used to avoid re-opening the same source partition if it is not actually + // error corrected. + bool source_ecc_open_failure_{false}; +}; + +namespace partition_writer { +// Return a PartitionWriter instance for perform InstallOps on this partition. +// Uses VABCPartitionWriter for Virtual AB Compression +std::unique_ptr<PartitionWriter> CreatePartitionWriter( + const PartitionUpdate& partition_update, + const InstallPlan::Partition& install_part, + DynamicPartitionControlInterface* dynamic_control, + size_t block_size, + bool is_interactive, + bool is_dynamic_partition); +} // namespace partition_writer +} // namespace chromeos_update_engine + +#endif diff --git a/payload_consumer/partition_writer_factory_android.cc b/payload_consumer/partition_writer_factory_android.cc new file mode 100644 index 00000000..184e2d5b --- /dev/null +++ b/payload_consumer/partition_writer_factory_android.cc @@ -0,0 +1,53 @@ +// +// 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 <cstddef> +#include <memory> + +#include <base/logging.h> + +#include "update_engine/payload_consumer/vabc_partition_writer.h" + +namespace chromeos_update_engine::partition_writer { + +std::unique_ptr<PartitionWriter> CreatePartitionWriter( + const PartitionUpdate& partition_update, + const InstallPlan::Partition& install_part, + DynamicPartitionControlInterface* dynamic_control, + size_t block_size, + bool is_interactive, + bool is_dynamic_partition) { + if (dynamic_control && dynamic_control->UpdateUsesSnapshotCompression() && + is_dynamic_partition) { + LOG(INFO) + << "Virtual AB Compression Enabled, using VABC Partition Writer for `" + << install_part.name << '`'; + return std::make_unique<VABCPartitionWriter>(partition_update, + install_part, + dynamic_control, + block_size, + is_interactive); + } else { + LOG(INFO) << "Virtual AB Compression disabled, using Partition Writer for `" + << install_part.name << '`'; + return std::make_unique<PartitionWriter>(partition_update, + install_part, + dynamic_control, + block_size, + is_interactive); + } +} +} // namespace chromeos_update_engine::partition_writer diff --git a/payload_consumer/partition_writer_factory_chromeos.cc b/payload_consumer/partition_writer_factory_chromeos.cc new file mode 100644 index 00000000..609f0431 --- /dev/null +++ b/payload_consumer/partition_writer_factory_chromeos.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 <cstddef> +#include <memory> + +#include <base/logging.h> + +#include "update_engine/payload_consumer/partition_writer.h" + +namespace chromeos_update_engine::partition_writer { +std::unique_ptr<PartitionWriter> CreatePartitionWriter( + const PartitionUpdate& partition_update, + const InstallPlan::Partition& install_part, + DynamicPartitionControlInterface* dynamic_control, + size_t block_size, + bool is_interactive, + bool is_dynamic_partition) { + return std::make_unique<PartitionWriter>(partition_update, + install_part, + dynamic_control, + block_size, + is_interactive); +} +} // namespace chromeos_update_engine::partition_writer diff --git a/payload_consumer/partition_writer_unittest.cc b/payload_consumer/partition_writer_unittest.cc new file mode 100644 index 00000000..263f338e --- /dev/null +++ b/payload_consumer/partition_writer_unittest.cc @@ -0,0 +1,204 @@ +// +// 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 <memory> +#include <vector> + +#include <brillo/secure_blob.h> +#include <gtest/gtest.h> + +#include "update_engine/common/dynamic_partition_control_stub.h" +#include "update_engine/common/error_code.h" +#include "update_engine/common/fake_prefs.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/delta_performer.h" +#include "update_engine/payload_consumer/extent_reader.h" +#include "update_engine/payload_consumer/extent_writer.h" +#include "update_engine/payload_consumer/fake_file_descriptor.h" +#include "update_engine/payload_consumer/file_descriptor.h" +#include "update_engine/payload_consumer/install_plan.h" +#include "update_engine/payload_generator/annotated_operation.h" +#include "update_engine/payload_generator/delta_diff_generator.h" +#include "update_engine/payload_generator/extent_ranges.h" +#include "update_engine/payload_generator/payload_file.h" +#include "update_engine/payload_generator/payload_generation_config.h" +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { + +class PartitionWriterTest : public testing::Test { + public: + // Helper function to pretend that the ECC file descriptor was already opened. + // Returns a pointer to the created file descriptor. + FakeFileDescriptor* SetFakeECCFile(size_t size) { + EXPECT_FALSE(writer_.source_ecc_fd_) << "source_ecc_fd_ already open."; + FakeFileDescriptor* ret = new FakeFileDescriptor(); + fake_ecc_fd_.reset(ret); + // Call open to simulate it was already opened. + ret->Open("", 0); + ret->SetFileSize(size); + writer_.source_ecc_fd_ = fake_ecc_fd_; + return ret; + } + + uint64_t GetSourceEccRecoveredFailures() const { + return writer_.source_ecc_recovered_failures_; + } + + AnnotatedOperation GenerateSourceCopyOp(const brillo::Blob& copied_data, + bool add_hash, + PartitionConfig* old_part = nullptr) { + PayloadGenerationConfig config; + const uint64_t kDefaultBlockSize = config.block_size; + EXPECT_EQ(0U, copied_data.size() % kDefaultBlockSize); + uint64_t num_blocks = copied_data.size() / kDefaultBlockSize; + AnnotatedOperation aop; + *(aop.op.add_src_extents()) = ExtentForRange(0, num_blocks); + *(aop.op.add_dst_extents()) = ExtentForRange(0, num_blocks); + aop.op.set_type(InstallOperation::SOURCE_COPY); + brillo::Blob src_hash; + EXPECT_TRUE(HashCalculator::RawHashOfData(copied_data, &src_hash)); + if (add_hash) + aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size()); + + return aop; + } + + brillo::Blob PerformSourceCopyOp(const InstallOperation& op, + const brillo::Blob blob_data) { + ScopedTempFile source_partition("Blob-XXXXXX"); + FileDescriptorPtr fd(new EintrSafeFileDescriptor()); + DirectExtentWriter extent_writer{fd}; + EXPECT_TRUE(fd->Open(source_partition.path().c_str(), O_RDWR)); + EXPECT_TRUE(extent_writer.Init(op.src_extents(), kBlockSize)); + EXPECT_TRUE(extent_writer.Write(blob_data.data(), blob_data.size())); + + ScopedTempFile target_partition("Blob-XXXXXX"); + + install_part_.source_path = source_partition.path(); + install_part_.target_path = target_partition.path(); + install_part_.source_size = blob_data.size(); + install_part_.target_size = blob_data.size(); + + ErrorCode error; + EXPECT_TRUE(writer_.Init(&install_plan_, true, 0)); + EXPECT_TRUE(writer_.PerformSourceCopyOperation(op, &error)); + writer_.CheckpointUpdateProgress(1); + + brillo::Blob output_data; + EXPECT_TRUE(utils::ReadFile(target_partition.path(), &output_data)); + return output_data; + } + + FakePrefs prefs_{}; + InstallPlan install_plan_{}; + InstallPlan::Payload payload_{}; + DynamicPartitionControlStub dynamic_control_{}; + FileDescriptorPtr fake_ecc_fd_{}; + DeltaArchiveManifest manifest_{}; + PartitionUpdate partition_update_{}; + InstallPlan::Partition install_part_{}; + PartitionWriter writer_{ + partition_update_, install_part_, &dynamic_control_, kBlockSize, false}; +}; +// Test that the error-corrected file descriptor is used to read a partition +// when no hash is available for SOURCE_COPY but it falls back to the normal +// file descriptor when the size of the error corrected one is too small. +TEST_F(PartitionWriterTest, ErrorCorrectionSourceCopyWhenNoHashFallbackTest) { + constexpr size_t kCopyOperationSize = 4 * 4096; + ScopedTempFile source("Source-XXXXXX"); + // Setup the source path with the right expected data. + brillo::Blob expected_data = FakeFileDescriptorData(kCopyOperationSize); + EXPECT_TRUE(test_utils::WriteFileVector(source.path(), expected_data)); + + // Setup the fec file descriptor as the fake stream, with smaller data than + // 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. + auto source_copy_op = GenerateSourceCopyOp(expected_data, false, &old_part); + + auto output_data = PerformSourceCopyOp(source_copy_op.op, expected_data); + ASSERT_EQ(output_data, expected_data); + + // 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 + // reached the EOF. + EXPECT_LE(1U, fake_fec->GetReadOps().size()); + // This fallback doesn't count as an error-corrected operation since the + // operation hash was not available. + EXPECT_EQ(0U, GetSourceEccRecoveredFailures()); +} + +// Test that the error-corrected file descriptor is used to read the partition +// since the source partition doesn't match the operation hash. +TEST_F(PartitionWriterTest, ErrorCorrectionSourceCopyFallbackTest) { + constexpr size_t kCopyOperationSize = 4 * 4096; + // Write invalid data to the source image, which doesn't match the expected + // hash. + brillo::Blob invalid_data(kCopyOperationSize, 0x55); + + // Setup the fec file descriptor as the fake stream, which matches + // |expected_data|. + FakeFileDescriptor* fake_fec = SetFakeECCFile(kCopyOperationSize); + brillo::Blob expected_data = FakeFileDescriptorData(kCopyOperationSize); + + auto source_copy_op = GenerateSourceCopyOp(expected_data, true); + auto output_data = PerformSourceCopyOp(source_copy_op.op, invalid_data); + ASSERT_EQ(output_data, expected_data); + + // Verify that the fake_fec was actually used. + EXPECT_EQ(1U, fake_fec->GetReadOps().size()); + EXPECT_EQ(1U, GetSourceEccRecoveredFailures()); +} + +TEST_F(PartitionWriterTest, ChooseSourceFDTest) { + constexpr size_t kSourceSize = 4 * 4096; + ScopedTempFile source("Source-XXXXXX"); + // Write invalid data to the source image, which doesn't match the expected + // hash. + brillo::Blob invalid_data(kSourceSize, 0x55); + EXPECT_TRUE(test_utils::WriteFileVector(source.path(), invalid_data)); + + writer_.source_fd_ = std::make_shared<EintrSafeFileDescriptor>(); + writer_.source_fd_->Open(source.path().c_str(), O_RDONLY); + + // Setup the fec file descriptor as the fake stream, which matches + // |expected_data|. + FakeFileDescriptor* fake_fec = SetFakeECCFile(kSourceSize); + brillo::Blob expected_data = FakeFileDescriptorData(kSourceSize); + + InstallOperation op; + *(op.add_src_extents()) = ExtentForRange(0, kSourceSize / 4096); + brillo::Blob src_hash; + EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash)); + op.set_src_sha256_hash(src_hash.data(), src_hash.size()); + + ErrorCode error = ErrorCode::kSuccess; + EXPECT_EQ(writer_.source_ecc_fd_, writer_.ChooseSourceFD(op, &error)); + EXPECT_EQ(ErrorCode::kSuccess, error); + // Verify that the fake_fec was actually used. + EXPECT_EQ(1U, fake_fec->GetReadOps().size()); + EXPECT_EQ(1U, GetSourceEccRecoveredFailures()); +} + +} // namespace chromeos_update_engine diff --git a/payload_consumer/payload_constants.cc b/payload_consumer/payload_constants.cc index a2368a43..d62a0ec6 100644 --- a/payload_consumer/payload_constants.cc +++ b/payload_consumer/payload_constants.cc @@ -16,24 +16,28 @@ #include "update_engine/payload_consumer/payload_constants.h" +#include <base/logging.h> + namespace chromeos_update_engine { -const uint64_t kChromeOSMajorPayloadVersion = 1; +// const uint64_t kChromeOSMajorPayloadVersion = 1; DEPRECATED const uint64_t kBrilloMajorPayloadVersion = 2; -const uint32_t kMinSupportedMinorPayloadVersion = 1; -const uint32_t kMaxSupportedMinorPayloadVersion = 6; +const uint64_t kMinSupportedMajorPayloadVersion = kBrilloMajorPayloadVersion; +const uint64_t kMaxSupportedMajorPayloadVersion = kBrilloMajorPayloadVersion; const uint32_t kFullPayloadMinorVersion = 0; -const uint32_t kInPlaceMinorPayloadVersion = 1; +// const uint32_t kInPlaceMinorPayloadVersion = 1; DEPRECATED const uint32_t kSourceMinorPayloadVersion = 2; 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 uint64_t kMinSupportedMajorPayloadVersion = 1; -const uint64_t kMaxSupportedMajorPayloadVersion = 2; +const uint32_t kMinSupportedMinorPayloadVersion = kSourceMinorPayloadVersion; +const uint32_t kMaxSupportedMinorPayloadVersion = + kPartialUpdateMinorPayloadVersion; const uint64_t kMaxPayloadHeaderSize = 24; @@ -44,10 +48,6 @@ const char kDeltaMagic[4] = {'C', 'r', 'A', 'U'}; const char* InstallOperationTypeName(InstallOperation::Type op_type) { switch (op_type) { - case InstallOperation::BSDIFF: - return "BSDIFF"; - case InstallOperation::MOVE: - return "MOVE"; case InstallOperation::REPLACE: return "REPLACE"; case InstallOperation::REPLACE_BZ: @@ -66,6 +66,10 @@ const char* InstallOperationTypeName(InstallOperation::Type op_type) { return "PUFFDIFF"; case InstallOperation::BROTLI_BSDIFF: return "BROTLI_BSDIFF"; + + case InstallOperation::BSDIFF: + case InstallOperation::MOVE: + NOTREACHED(); } return "<unknown_op>"; } diff --git a/payload_consumer/payload_constants.h b/payload_consumer/payload_constants.h index 16424887..03647ee7 100644 --- a/payload_consumer/payload_constants.h +++ b/payload_consumer/payload_constants.h @@ -26,7 +26,7 @@ namespace chromeos_update_engine { // The major version used by Chrome OS. -extern const uint64_t kChromeOSMajorPayloadVersion; +// extern const uint64_t kChromeOSMajorPayloadVersion; DEPRECATED // The major version used by Brillo. extern const uint64_t kBrilloMajorPayloadVersion; @@ -39,7 +39,7 @@ extern const uint64_t kMaxSupportedMajorPayloadVersion; extern const uint32_t kFullPayloadMinorVersion; // The minor version used by the in-place delta generator algorithm. -extern const uint32_t kInPlaceMinorPayloadVersion; +// extern const uint32_t kInPlaceMinorPayloadVersion; DEPRECATED // The minor version used by the A to B delta generator algorithm. extern const uint32_t kSourceMinorPayloadVersion; @@ -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; diff --git a/payload_consumer/payload_metadata.cc b/payload_consumer/payload_metadata.cc index 19052b99..f797723c 100644 --- a/payload_consumer/payload_metadata.cc +++ b/payload_consumer/payload_metadata.cc @@ -38,40 +38,29 @@ const uint64_t PayloadMetadata::kDeltaManifestSizeOffset = const uint64_t PayloadMetadata::kDeltaManifestSizeSize = 8; const uint64_t PayloadMetadata::kDeltaMetadataSignatureSizeSize = 4; -bool PayloadMetadata::GetMetadataSignatureSizeOffset( - uint64_t* out_offset) const { - if (GetMajorVersion() == kBrilloMajorPayloadVersion) { - *out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize; - return true; - } - return false; +uint64_t PayloadMetadata::GetMetadataSignatureSizeOffset() const { + return kDeltaManifestSizeOffset + kDeltaManifestSizeSize; } -bool PayloadMetadata::GetManifestOffset(uint64_t* out_offset) const { - // Actual manifest begins right after the manifest size field or - // metadata signature size field if major version >= 2. - if (major_payload_version_ == kChromeOSMajorPayloadVersion) { - *out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize; - return true; - } - if (major_payload_version_ == kBrilloMajorPayloadVersion) { - *out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize + - kDeltaMetadataSignatureSizeSize; - return true; - } - LOG(ERROR) << "Unknown major payload version: " << major_payload_version_; - return false; +uint64_t PayloadMetadata::GetManifestOffset() const { + // Actual manifest begins right after the metadata signature size field. + return kDeltaManifestSizeOffset + kDeltaManifestSizeSize + + kDeltaMetadataSignatureSizeSize; } MetadataParseResult PayloadMetadata::ParsePayloadHeader( const brillo::Blob& payload, ErrorCode* error) { - uint64_t manifest_offset; + return ParsePayloadHeader(payload.data(), payload.size(), error); +} + +MetadataParseResult PayloadMetadata::ParsePayloadHeader( + const unsigned char* payload, size_t size, ErrorCode* error) { // Ensure we have data to cover the major payload version. - if (payload.size() < kDeltaManifestSizeOffset) + if (size < kDeltaManifestSizeOffset) return MetadataParseResult::kInsufficientData; // Validate the magic string. - if (memcmp(payload.data(), kDeltaMagic, sizeof(kDeltaMagic)) != 0) { + if (memcmp(payload, kDeltaMagic, sizeof(kDeltaMagic)) != 0) { LOG(ERROR) << "Bad payload format -- invalid delta magic: " << base::StringPrintf("%02x%02x%02x%02x", payload[0], @@ -88,6 +77,11 @@ MetadataParseResult PayloadMetadata::ParsePayloadHeader( return MetadataParseResult::kError; } + uint64_t manifest_offset = GetManifestOffset(); + // Check again with the manifest offset. + if (size < manifest_offset) + return MetadataParseResult::kInsufficientData; + // Extract the payload version from the metadata. static_assert(sizeof(major_payload_version_) == kDeltaVersionSize, "Major payload version size mismatch"); @@ -105,15 +99,6 @@ MetadataParseResult PayloadMetadata::ParsePayloadHeader( return MetadataParseResult::kError; } - // Get the manifest offset now that we have payload version. - if (!GetManifestOffset(&manifest_offset)) { - *error = ErrorCode::kUnsupportedMajorPayloadVersion; - return MetadataParseResult::kError; - } - // Check again with the manifest offset. - if (payload.size() < manifest_offset) - return MetadataParseResult::kInsufficientData; - // Next, parse the manifest size. static_assert(sizeof(manifest_size_) == kDeltaManifestSizeSize, "manifest_size size mismatch"); @@ -125,30 +110,26 @@ MetadataParseResult PayloadMetadata::ParsePayloadHeader( metadata_size_ = manifest_offset + manifest_size_; if (metadata_size_ < manifest_size_) { // Overflow detected. + LOG(ERROR) << "Overflow detected on manifest size."; *error = ErrorCode::kDownloadInvalidMetadataSize; return MetadataParseResult::kError; } - if (GetMajorVersion() == kBrilloMajorPayloadVersion) { - // Parse the metadata signature size. - static_assert( - sizeof(metadata_signature_size_) == kDeltaMetadataSignatureSizeSize, - "metadata_signature_size size mismatch"); - uint64_t metadata_signature_size_offset; - if (!GetMetadataSignatureSizeOffset(&metadata_signature_size_offset)) { - *error = ErrorCode::kError; - return MetadataParseResult::kError; - } - memcpy(&metadata_signature_size_, - &payload[metadata_signature_size_offset], - kDeltaMetadataSignatureSizeSize); - metadata_signature_size_ = be32toh(metadata_signature_size_); - - if (metadata_size_ + metadata_signature_size_ < metadata_size_) { - // Overflow detected. - *error = ErrorCode::kDownloadInvalidMetadataSize; - return MetadataParseResult::kError; - } + // Parse the metadata signature size. + static_assert( + sizeof(metadata_signature_size_) == kDeltaMetadataSignatureSizeSize, + "metadata_signature_size size mismatch"); + uint64_t metadata_signature_size_offset = GetMetadataSignatureSizeOffset(); + memcpy(&metadata_signature_size_, + &payload[metadata_signature_size_offset], + kDeltaMetadataSignatureSizeSize); + metadata_signature_size_ = be32toh(metadata_signature_size_); + + if (metadata_size_ + metadata_signature_size_ < metadata_size_) { + // Overflow detected. + LOG(ERROR) << "Overflow detected on metadata and signature size."; + *error = ErrorCode::kDownloadInvalidMetadataSize; + return MetadataParseResult::kError; } return MetadataParseResult::kSuccess; } @@ -160,10 +141,14 @@ bool PayloadMetadata::ParsePayloadHeader(const brillo::Blob& payload) { bool PayloadMetadata::GetManifest(const brillo::Blob& payload, DeltaArchiveManifest* out_manifest) const { - uint64_t manifest_offset; - if (!GetManifestOffset(&manifest_offset)) - return false; - CHECK_GE(payload.size(), manifest_offset + manifest_size_); + return GetManifest(payload.data(), payload.size(), out_manifest); +} + +bool PayloadMetadata::GetManifest(const unsigned char* payload, + size_t size, + DeltaArchiveManifest* out_manifest) const { + uint64_t manifest_offset = GetManifestOffset(); + CHECK_GE(size, manifest_offset + manifest_size_); return out_manifest->ParseFromArray(&payload[manifest_offset], manifest_size_); } @@ -188,7 +173,7 @@ ErrorCode PayloadMetadata::ValidateMetadataSignature( << metadata_signature; return ErrorCode::kDownloadMetadataSignatureError; } - } else if (major_payload_version_ == kBrilloMajorPayloadVersion) { + } else { metadata_signature_protobuf.assign( payload.begin() + metadata_size_, payload.begin() + metadata_size_ + metadata_signature_size_); @@ -237,4 +222,32 @@ ErrorCode PayloadMetadata::ValidateMetadataSignature( return ErrorCode::kSuccess; } +bool PayloadMetadata::ParsePayloadFile(const string& payload_path, + DeltaArchiveManifest* manifest, + Signatures* metadata_signatures) { + brillo::Blob payload; + TEST_AND_RETURN_FALSE( + utils::ReadFileChunk(payload_path, 0, kMaxPayloadHeaderSize, &payload)); + TEST_AND_RETURN_FALSE(ParsePayloadHeader(payload)); + + if (manifest != nullptr) { + TEST_AND_RETURN_FALSE( + utils::ReadFileChunk(payload_path, + kMaxPayloadHeaderSize, + GetMetadataSize() - kMaxPayloadHeaderSize, + &payload)); + TEST_AND_RETURN_FALSE(GetManifest(payload, manifest)); + } + + if (metadata_signatures != nullptr) { + payload.clear(); + TEST_AND_RETURN_FALSE(utils::ReadFileChunk( + payload_path, GetMetadataSize(), GetMetadataSignatureSize(), &payload)); + TEST_AND_RETURN_FALSE( + metadata_signatures->ParseFromArray(payload.data(), payload.size())); + } + + return true; +} + } // namespace chromeos_update_engine diff --git a/payload_consumer/payload_metadata.h b/payload_consumer/payload_metadata.h index 75ef8f90..f23b668f 100644 --- a/payload_consumer/payload_metadata.h +++ b/payload_consumer/payload_metadata.h @@ -56,6 +56,9 @@ class PayloadMetadata { // the payload. MetadataParseResult ParsePayloadHeader(const brillo::Blob& payload, ErrorCode* error); + MetadataParseResult ParsePayloadHeader(const unsigned char* payload, + size_t size, + ErrorCode* error); // Simpler version of the above, returns true on success. bool ParsePayloadHeader(const brillo::Blob& payload); @@ -63,7 +66,7 @@ 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( @@ -88,15 +91,24 @@ class PayloadMetadata { bool GetManifest(const brillo::Blob& payload, DeltaArchiveManifest* out_manifest) const; + bool GetManifest(const unsigned char* payload, + size_t size, + DeltaArchiveManifest* out_manifest) const; + + // Parses a payload file |payload_path| and prepares the metadata properties, + // manifest and metadata signatures. Can be used as an easy to use utility to + // get the payload information without manually the process. + bool ParsePayloadFile(const std::string& payload_path, + DeltaArchiveManifest* manifest, + Signatures* metadata_signatures); + private: - // Set |*out_offset| to the byte offset at which the manifest protobuf begins - // in a payload. Return true on success, false if the offset is unknown. - bool GetManifestOffset(uint64_t* out_offset) const; - - // Set |*out_offset| to the byte offset where the size of the metadata - // signature is stored in a payload. Return true on success, if this field is - // not present in the payload, return false. - bool GetMetadataSignatureSizeOffset(uint64_t* out_offset) const; + // Returns the byte offset at which the manifest protobuf begins in a payload. + uint64_t GetManifestOffset() const; + + // Returns the byte offset where the size of the metadata signature is stored + // in a payload. + uint64_t GetMetadataSignatureSizeOffset() const; uint64_t metadata_size_{0}; uint64_t manifest_size_{0}; diff --git a/payload_consumer/payload_verifier.cc b/payload_consumer/payload_verifier.cc index 24e337ec..8a3ea650 100644 --- a/payload_consumer/payload_verifier.cc +++ b/payload_consumer/payload_verifier.cc @@ -172,9 +172,7 @@ bool PayloadVerifier::VerifyRawSignature( if (padded_hash_data == sig_hash_data) { return true; } - } - - if (key_type == EVP_PKEY_EC) { + } else if (key_type == EVP_PKEY_EC) { EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(public_key.get()); TEST_AND_RETURN_FALSE(ec_key != nullptr); if (ECDSA_verify(0, @@ -185,10 +183,10 @@ bool PayloadVerifier::VerifyRawSignature( ec_key) == 1) { return true; } + } else { + LOG(ERROR) << "Unsupported key type " << key_type; + return false; } - - LOG(ERROR) << "Unsupported key type " << key_type; - return false; } LOG(INFO) << "Failed to verify the signature with " << public_keys_.size() << " keys."; @@ -203,7 +201,7 @@ bool PayloadVerifier::GetRawHashFromSignature( // // openssl rsautl -verify -pubin -inkey <(echo pem_public_key) // -in |sig_data| -out |out_hash_data| - RSA* rsa = EVP_PKEY_get0_RSA(public_key); + RSA* rsa = EVP_PKEY_get0_RSA(const_cast<EVP_PKEY*>(public_key)); TEST_AND_RETURN_FALSE(rsa != nullptr); unsigned int keysize = RSA_size(rsa); diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc index c08cfc20..051ccbf7 100644 --- a/payload_consumer/postinstall_runner_action.cc +++ b/payload_consumer/postinstall_runner_action.cc @@ -28,6 +28,7 @@ #include <base/files/file_path.h> #include <base/files/file_util.h> #include <base/logging.h> +#include <base/stl_util.h> #include <base/strings/string_split.h> #include <base/strings/string_util.h> @@ -49,17 +50,48 @@ const int kPostinstallStatusFd = 3; namespace chromeos_update_engine { -using brillo::MessageLoop; using std::string; using std::vector; +PostinstallRunnerAction::PostinstallRunnerAction( + BootControlInterface* boot_control, HardwareInterface* hardware) + : boot_control_(boot_control), hardware_(hardware) { +#ifdef __ANDROID__ + fs_mount_dir_ = "/postinstall"; +#else // __ANDROID__ + base::FilePath temp_dir; + TEST_AND_RETURN(base::CreateNewTempDirectory("au_postint_mount", &temp_dir)); + fs_mount_dir_ = temp_dir.value(); +#endif // __ANDROID__ + CHECK(!fs_mount_dir_.empty()); + LOG(INFO) << "postinstall mount point: " << fs_mount_dir_; +} + void PostinstallRunnerAction::PerformAction() { CHECK(HasInputObject()); + CHECK(boot_control_); install_plan_ = GetInputObject(); - // Currently we're always powerwashing when rolling back. + auto dynamic_control = boot_control_->GetDynamicPartitionControl(); + CHECK(dynamic_control); + + // Mount snapshot partitions for Virtual AB Compression Compression. + if (dynamic_control->UpdateUsesSnapshotCompression()) { + // Before calling MapAllPartitions to map snapshot devices, all CowWriters + // must be closed, and MapAllPartitions() should be called. + dynamic_control->UnmapAllPartitions(); + if (!dynamic_control->MapAllPartitions()) { + return CompletePostinstall(ErrorCode::kPostInstallMountError); + } + } + + // We always powerwash when rolling back, however policy can determine + // if this is a full/normal powerwash, or a special rollback powerwash + // that retains a small amount of system state such as enrollment and + // network configuration. In both cases all user accounts are deleted. if (install_plan_.powerwash_required || install_plan_.is_rollback) { - if (hardware_->SchedulePowerwash(install_plan_.is_rollback)) { + if (hardware_->SchedulePowerwash( + install_plan_.rollback_data_save_requested)) { powerwash_scheduled_ = true; } else { return CompletePostinstall(ErrorCode::kPostinstallPowerwashError); @@ -108,8 +140,7 @@ void PostinstallRunnerAction::PerformPartitionPostinstall() { const InstallPlan::Partition& partition = install_plan_.partitions[current_partition_]; - const string mountable_device = - utils::MakePartitionNameForMount(partition.target_path); + const string mountable_device = partition.readonly_target_path; if (mountable_device.empty()) { LOG(ERROR) << "Cannot make mountable device from " << partition.target_path; return CompletePostinstall(ErrorCode::kPostinstallRunnerError); @@ -118,14 +149,12 @@ void PostinstallRunnerAction::PerformPartitionPostinstall() { // Perform post-install for the current_partition_ partition. At this point we // need to call CompletePartitionPostinstall to complete the operation and // cleanup. -#ifdef __ANDROID__ - fs_mount_dir_ = "/postinstall"; -#else // __ANDROID__ - base::FilePath temp_dir; - TEST_AND_RETURN(base::CreateNewTempDirectory("au_postint_mount", &temp_dir)); - fs_mount_dir_ = temp_dir.value(); -#endif // __ANDROID__ + if (!utils::FileExists(fs_mount_dir_.c_str())) { + LOG(ERROR) << "Mount point " << fs_mount_dir_ + << " does not exist, mount call will fail"; + return CompletePostinstall(ErrorCode::kPostinstallRunnerError); + } // Double check that the fs_mount_dir is not busy with a previous mounted // filesystem from a previous crashed postinstall step. if (utils::IsMountpoint(fs_mount_dir_)) { @@ -162,11 +191,12 @@ void PostinstallRunnerAction::PerformPartitionPostinstall() { } #endif // __ANDROID__ - if (!utils::MountFilesystem(mountable_device, - fs_mount_dir_, - MS_RDONLY, - partition.filesystem_type, - constants::kPostinstallMountOptions)) { + if (!utils::MountFilesystem( + mountable_device, + fs_mount_dir_, + MS_RDONLY, + partition.filesystem_type, + hardware_->GetPartitionMountOptions(partition.name))) { return CompletePartitionPostinstall( 1, "Error mounting the device " + mountable_device); } @@ -215,13 +245,10 @@ void PostinstallRunnerAction::PerformPartitionPostinstall() { PLOG(ERROR) << "Unable to set non-blocking I/O mode on fd " << progress_fd_; } - progress_task_ = MessageLoop::current()->WatchFileDescriptor( - FROM_HERE, + progress_controller_ = base::FileDescriptorWatcher::WatchReadable( progress_fd_, - MessageLoop::WatchMode::kWatchRead, - true, - base::Bind(&PostinstallRunnerAction::OnProgressFdReady, - base::Unretained(this))); + base::BindRepeating(&PostinstallRunnerAction::OnProgressFdReady, + base::Unretained(this))); } void PostinstallRunnerAction::OnProgressFdReady() { @@ -231,7 +258,7 @@ void PostinstallRunnerAction::OnProgressFdReady() { bytes_read = 0; bool eof; bool ok = - utils::ReadAll(progress_fd_, buf, arraysize(buf), &bytes_read, &eof); + utils::ReadAll(progress_fd_, buf, base::size(buf), &bytes_read, &eof); progress_buffer_.append(buf, bytes_read); // Process every line. vector<string> lines = base::SplitString( @@ -246,8 +273,7 @@ void PostinstallRunnerAction::OnProgressFdReady() { if (!ok || eof) { // There was either an error or an EOF condition, so we are done watching // the file descriptor. - MessageLoop::current()->CancelTask(progress_task_); - progress_task_ = MessageLoop::kTaskIdNull; + progress_controller_.reset(); return; } } while (bytes_read); @@ -284,17 +310,18 @@ void PostinstallRunnerAction::ReportProgress(double frac) { void PostinstallRunnerAction::Cleanup() { utils::UnmountFilesystem(fs_mount_dir_); #ifndef __ANDROID__ - if (!base::DeleteFile(base::FilePath(fs_mount_dir_), false)) { +#if BASE_VER < 800000 + if (!base::DeleteFile(base::FilePath(fs_mount_dir_), true)) { +#else + if (!base::DeleteFile(base::FilePath(fs_mount_dir_))) { +#endif PLOG(WARNING) << "Not removing temporary mountpoint " << fs_mount_dir_; } -#endif // !__ANDROID__ - fs_mount_dir_.clear(); +#endif progress_fd_ = -1; - if (progress_task_ != MessageLoop::kTaskIdNull) { - MessageLoop::current()->CancelTask(progress_task_); - progress_task_ = MessageLoop::kTaskIdNull; - } + progress_controller_.reset(); + progress_buffer_.clear(); } @@ -348,12 +375,19 @@ void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) { } else { // Schedules warm reset on next reboot, ignores the error. hardware_->SetWarmReset(true); + // Sets the vbmeta digest for the other slot to boot into. + hardware_->SetVbmetaDigestForInactiveSlot(false); } } else { error_code = ErrorCode::kUpdatedButNotActive; } } + auto dynamic_control = boot_control_->GetDynamicPartitionControl(); + CHECK(dynamic_control); + dynamic_control->UnmapAllPartitions(); + LOG(INFO) << "Unmapped all partitions."; + ScopedActionCompleter completer(processor_, this); completer.set_code(error_code); diff --git a/payload_consumer/postinstall_runner_action.h b/payload_consumer/postinstall_runner_action.h index b9b7069c..178d72a2 100644 --- a/payload_consumer/postinstall_runner_action.h +++ b/payload_consumer/postinstall_runner_action.h @@ -17,9 +17,12 @@ #ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_POSTINSTALL_RUNNER_ACTION_H_ #define UPDATE_ENGINE_PAYLOAD_CONSUMER_POSTINSTALL_RUNNER_ACTION_H_ +#include <memory> #include <string> +#include <utility> #include <vector> +#include <base/files/file_descriptor_watcher_posix.h> #include <brillo/message_loops/message_loop.h> #include <gtest/gtest_prod.h> @@ -38,8 +41,7 @@ class BootControlInterface; class PostinstallRunnerAction : public InstallPlanAction { public: PostinstallRunnerAction(BootControlInterface* boot_control, - HardwareInterface* hardware) - : boot_control_(boot_control), hardware_(hardware) {} + HardwareInterface* hardware); // InstallPlanAction overrides. void PerformAction() override; @@ -66,6 +68,9 @@ class PostinstallRunnerAction : public InstallPlanAction { friend class PostinstallRunnerActionTest; FRIEND_TEST(PostinstallRunnerActionTest, ProcessProgressLineTest); + // exposed for testing purposes only + void SetMountDir(std::string dir) { fs_mount_dir_ = std::move(dir); } + void PerformPartitionPostinstall(); // Called whenever the |progress_fd_| has data available to read. @@ -95,8 +100,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_; @@ -139,7 +142,8 @@ 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}; - brillo::MessageLoop::TaskId progress_task_{brillo::MessageLoop::kTaskIdNull}; + + std::unique_ptr<base::FileDescriptorWatcher::Controller> progress_controller_; // A buffer of a partial read line from the progress file descriptor. std::string progress_buffer_; diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc index e9313f1a..792ee282 100644 --- a/payload_consumer/postinstall_runner_action_unittest.cc +++ b/payload_consumer/postinstall_runner_action_unittest.cc @@ -26,9 +26,14 @@ #include <base/bind.h> #include <base/files/file_util.h> +#if BASE_VER < 780000 // Android #include <base/message_loop/message_loop.h> +#endif // BASE_VER < 780000 #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> +#if BASE_VER >= 780000 // CrOS +#include <base/task/single_thread_task_executor.h> +#endif // BASE_VER >= 780000 #include <brillo/message_loops/base_message_loop.h> #include <brillo/message_loops/message_loop_utils.h> #include <gmock/gmock.h> @@ -40,7 +45,7 @@ #include "update_engine/common/subprocess.h" #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" -#include "update_engine/mock_payload_state.h" +#include "update_engine/cros/mock_payload_state.h" using brillo::MessageLoop; using chromeos_update_engine::test_utils::ScopedLoopbackDeviceBinder; @@ -100,7 +105,8 @@ class PostinstallRunnerActionTest : public ::testing::Test { void RunPostinstallAction(const string& device_path, const string& postinstall_program, bool powerwash_required, - bool is_rollback); + bool is_rollback, + bool save_rollback_data); void RunPostinstallActionWithInstallPlan(const InstallPlan& install_plan); @@ -143,13 +149,25 @@ class PostinstallRunnerActionTest : public ::testing::Test { base::TimeDelta::FromMilliseconds(10)); } else { CHECK(processor_); - processor_->StopProcessing(); + // Must |PostDelayedTask()| here to be safe that |FileDescriptorWatcher| + // doesn't leak memory, do not directly call |StopProcessing()|. + loop_.PostDelayedTask( + FROM_HERE, + base::Bind( + [](ActionProcessor* processor) { processor->StopProcessing(); }, + base::Unretained(processor_)), + base::TimeDelta::FromMilliseconds(100)); } } protected: +#if BASE_VER < 780000 // Android base::MessageLoopForIO base_loop_; brillo::BaseMessageLoop loop_{&base_loop_}; +#else // CrOS + base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO}; + brillo::BaseMessageLoop loop_{base_loop_.task_runner()}; +#endif // BASE_VER < 780000 brillo::AsynchronousSignalHandler async_signal_handler_; Subprocess subprocess_; @@ -172,10 +190,12 @@ void PostinstallRunnerActionTest::RunPostinstallAction( const string& device_path, const string& postinstall_program, bool powerwash_required, - bool is_rollback) { + bool is_rollback, + bool save_rollback_data) { InstallPlan::Partition part; part.name = "part"; part.target_path = device_path; + part.readonly_target_path = device_path; part.run_postinstall = true; part.postinstall_path = postinstall_program; InstallPlan install_plan; @@ -183,6 +203,7 @@ void PostinstallRunnerActionTest::RunPostinstallAction( install_plan.download_url = "http://127.0.0.1:8080/update"; install_plan.powerwash_required = powerwash_required; install_plan.is_rollback = is_rollback; + install_plan.rollback_data_save_requested = save_rollback_data; RunPostinstallActionWithInstallPlan(install_plan); } @@ -195,6 +216,9 @@ void PostinstallRunnerActionTest::RunPostinstallActionWithInstallPlan( auto runner_action = std::make_unique<PostinstallRunnerAction>( &fake_boot_control_, &fake_hardware_); postinstall_action_ = runner_action.get(); + base::FilePath temp_dir; + TEST_AND_RETURN(base::CreateNewTempDirectory("postinstall", &temp_dir)); + postinstall_action_->SetMountDir(temp_dir.value()); runner_action->set_delegate(setup_action_delegate_); BondActions(feeder_action.get(), runner_action.get()); auto collector_action = @@ -217,7 +241,7 @@ void PostinstallRunnerActionTest::RunPostinstallActionWithInstallPlan( 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_); } } @@ -256,7 +280,8 @@ TEST_F(PostinstallRunnerActionTest, ProcessProgressLineTest) { TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) { ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); - RunPostinstallAction(loop.dev(), kPostinstallDefaultScript, false, false); + RunPostinstallAction( + loop.dev(), kPostinstallDefaultScript, false, false, false); EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); EXPECT_TRUE(processor_delegate_.processing_done_called_); @@ -267,7 +292,7 @@ TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) { TEST_F(PostinstallRunnerActionTest, RunAsRootRunSymlinkFileTest) { ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); - RunPostinstallAction(loop.dev(), "bin/postinst_link", false, false); + RunPostinstallAction(loop.dev(), "bin/postinst_link", false, false, false); EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); } @@ -277,6 +302,7 @@ TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) { RunPostinstallAction(loop.dev(), "bin/postinst_example", /*powerwash_required=*/true, + false, false); EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); @@ -285,14 +311,31 @@ TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) { EXPECT_FALSE(fake_hardware_.GetIsRollbackPowerwashScheduled()); } -TEST_F(PostinstallRunnerActionTest, RunAsRootRollbackTest) { +TEST_F(PostinstallRunnerActionTest, RunAsRootRollbackTestNoDataSave) { + ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); + + // Run a simple postinstall program, rollback happened. + RunPostinstallAction(loop.dev(), + "bin/postinst_example", + false, + /*is_rollback=*/true, + /*save_rollback_data=*/false); + EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); + + // Check that powerwash was scheduled and that it's NOT a rollback powerwash. + EXPECT_TRUE(fake_hardware_.IsPowerwashScheduled()); + EXPECT_FALSE(fake_hardware_.GetIsRollbackPowerwashScheduled()); +} + +TEST_F(PostinstallRunnerActionTest, RunAsRootRollbackTestWithDataSave) { ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); // Run a simple postinstall program, rollback happened. RunPostinstallAction(loop.dev(), "bin/postinst_example", false, - /*is_rollback=*/true); + /*is_rollback=*/true, + /*save_rollback_data=*/true); EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); // Check that powerwash was scheduled and that it's a rollback powerwash. @@ -303,7 +346,8 @@ TEST_F(PostinstallRunnerActionTest, RunAsRootRollbackTest) { // Runs postinstall from a partition file that doesn't mount, so it should // fail. TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) { - RunPostinstallAction("/dev/null", kPostinstallDefaultScript, false, false); + RunPostinstallAction( + "/dev/null", kPostinstallDefaultScript, false, false, false); EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_); // In case of failure, Postinstall should not signal a powerwash even if it @@ -316,6 +360,7 @@ TEST_F(PostinstallRunnerActionTest, RunAsRootSkipOptionalPostinstallTest) { InstallPlan::Partition part; part.name = "part"; part.target_path = "/dev/null"; + part.readonly_target_path = "/dev/null"; part.run_postinstall = true; part.postinstall_path = kPostinstallDefaultScript; part.postinstall_optional = true; @@ -337,7 +382,7 @@ TEST_F(PostinstallRunnerActionTest, RunAsRootSkipOptionalPostinstallTest) { // fail. TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) { ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); - RunPostinstallAction(loop.dev(), "bin/postinst_fail1", false, false); + RunPostinstallAction(loop.dev(), "bin/postinst_fail1", false, false, false); EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_); } @@ -345,7 +390,7 @@ TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) { // UMA with a different error code. Test those cases are properly detected. TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) { ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); - RunPostinstallAction(loop.dev(), "bin/postinst_fail3", false, false); + RunPostinstallAction(loop.dev(), "bin/postinst_fail3", false, false, false); EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB, processor_delegate_.code_); } @@ -353,16 +398,26 @@ TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) { // Check that you can't specify an absolute path. TEST_F(PostinstallRunnerActionTest, RunAsRootAbsolutePathNotAllowedTest) { ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); - RunPostinstallAction(loop.dev(), "/etc/../bin/sh", false, false); + RunPostinstallAction(loop.dev(), "/etc/../bin/sh", false, false, false); EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_); } #ifdef __ANDROID__ -// Check that the postinstall file is relabeled to the postinstall label. +// Check that the postinstall file is labeled to the postinstall_exec label. // SElinux labels are only set on Android. TEST_F(PostinstallRunnerActionTest, RunAsRootCheckFileContextsTest) { ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); - RunPostinstallAction(loop.dev(), "bin/self_check_context", false, false); + RunPostinstallAction( + loop.dev(), "bin/self_check_context", false, false, false); + EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); +} + +// Check that the postinstall file is relabeled to the default postinstall +// label. SElinux labels are only set on Android. +TEST_F(PostinstallRunnerActionTest, RunAsRootCheckDefaultFileContextsTest) { + ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); + RunPostinstallAction( + loop.dev(), "bin/self_check_default_context", false, false, false); EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); } #endif // __ANDROID__ @@ -375,7 +430,7 @@ TEST_F(PostinstallRunnerActionTest, RunAsRootSuspendResumeActionTest) { loop_.PostTask(FROM_HERE, base::Bind(&PostinstallRunnerActionTest::SuspendRunningAction, base::Unretained(this))); - RunPostinstallAction(loop.dev(), "bin/postinst_suspend", false, false); + RunPostinstallAction(loop.dev(), "bin/postinst_suspend", false, false, false); // postinst_suspend returns 0 only if it was suspended at some point. EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); EXPECT_TRUE(processor_delegate_.processing_done_called_); @@ -387,7 +442,7 @@ TEST_F(PostinstallRunnerActionTest, RunAsRootCancelPostinstallActionTest) { // Wait for the action to start and then cancel it. CancelWhenStarted(); - RunPostinstallAction(loop.dev(), "bin/postinst_suspend", false, false); + RunPostinstallAction(loop.dev(), "bin/postinst_suspend", false, false, false); // When canceling the action, the action never finished and therefore we had // a ProcessingStopped call instead. EXPECT_FALSE(processor_delegate_.code_set_); @@ -410,7 +465,8 @@ TEST_F(PostinstallRunnerActionTest, RunAsRootProgressUpdatesTest) { ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); setup_action_delegate_ = &mock_delegate_; - RunPostinstallAction(loop.dev(), "bin/postinst_progress", false, false); + RunPostinstallAction( + loop.dev(), "bin/postinst_progress", false, false, false); EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); } diff --git a/payload_consumer/snapshot_extent_writer.cc b/payload_consumer/snapshot_extent_writer.cc new file mode 100644 index 00000000..242e7260 --- /dev/null +++ b/payload_consumer/snapshot_extent_writer.cc @@ -0,0 +1,123 @@ +// +// 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/snapshot_extent_writer.h" + +#include <algorithm> +#include <cstdint> + +#include <libsnapshot/cow_writer.h> + +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { + +SnapshotExtentWriter::SnapshotExtentWriter( + android::snapshot::ICowWriter* cow_writer) + : cow_writer_(cow_writer) { + CHECK_NE(cow_writer, nullptr); +} + +SnapshotExtentWriter::~SnapshotExtentWriter() { + CHECK(buffer_.empty()) << buffer_.size(); +} + +bool SnapshotExtentWriter::Init( + const google::protobuf::RepeatedPtrField<Extent>& extents, + uint32_t block_size) { + extents_ = extents; + cur_extent_idx_ = 0; + buffer_.clear(); + buffer_.reserve(block_size); + block_size_ = block_size; + return true; +} + +size_t SnapshotExtentWriter::ConsumeWithBuffer(const uint8_t* data, + size_t count) { + CHECK_LT(cur_extent_idx_, static_cast<size_t>(extents_.size())); + const auto& cur_extent = extents_[cur_extent_idx_]; + const auto cur_extent_size = cur_extent.num_blocks() * block_size_; + + if (buffer_.empty() && count >= cur_extent_size) { + if (!cow_writer_->AddRawBlocks( + cur_extent.start_block(), data, cur_extent_size)) { + LOG(ERROR) << "AddRawBlocks(" << cur_extent.start_block() << ", " << data + << ", " << cur_extent_size << ") failed."; + // return value is expected to be greater than 0. Return 0 to signal error + // condition + return 0; + } + if (!next_extent()) { + CHECK_EQ(count, cur_extent_size) + << "Exhausted all blocks, but still have " << count - cur_extent_size + << " bytes left"; + } + return cur_extent_size; + } + CHECK_LT(buffer_.size(), cur_extent_size) + << "Data left in buffer should never be >= cur_extent_size, otherwise " + "we should have send that data to CowWriter. Buffer size: " + << buffer_.size() << " current extent size: " << cur_extent_size; + size_t bytes_to_copy = + std::min<size_t>(count, cur_extent_size - buffer_.size()); + CHECK_GT(bytes_to_copy, 0U); + + buffer_.insert(buffer_.end(), data, data + bytes_to_copy); + CHECK_LE(buffer_.size(), cur_extent_size); + + if (buffer_.size() == cur_extent_size) { + if (!cow_writer_->AddRawBlocks( + cur_extent.start_block(), buffer_.data(), buffer_.size())) { + LOG(ERROR) << "AddRawBlocks(" << cur_extent.start_block() << ", " + << buffer_.data() << ", " << buffer_.size() << ") failed."; + return 0; + } + buffer_.clear(); + if (!next_extent()) { + CHECK_EQ(count, bytes_to_copy) << "Exhausted all blocks, but still have " + << count - bytes_to_copy << " bytes left"; + } + } + return bytes_to_copy; +} + +// Returns true on success. +// This will construct a COW_REPLACE operation and forward it to CowWriter. It +// is important that caller does not perform SOURCE_COPY operation on this +// class, otherwise raw data will be stored. Caller should find ways to use +// COW_COPY whenever possible. +bool SnapshotExtentWriter::Write(const void* bytes, size_t count) { + if (count == 0) { + return true; + } + CHECK_NE(extents_.size(), 0); + + auto data = static_cast<const uint8_t*>(bytes); + while (count > 0) { + auto bytes_written = ConsumeWithBuffer(data, count); + TEST_AND_RETURN_FALSE(bytes_written > 0); + data += bytes_written; + count -= bytes_written; + } + return true; +} + +bool SnapshotExtentWriter::next_extent() { + cur_extent_idx_++; + return cur_extent_idx_ < static_cast<size_t>(extents_.size()); +} +} // namespace chromeos_update_engine diff --git a/payload_consumer/snapshot_extent_writer.h b/payload_consumer/snapshot_extent_writer.h new file mode 100644 index 00000000..c3a948ee --- /dev/null +++ b/payload_consumer/snapshot_extent_writer.h @@ -0,0 +1,59 @@ +// +// 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_SNAPSHOT_EXTENT_WRITER_H_ +#define UPDATE_ENGINE_SNAPSHOT_EXTENT_WRITER_H_ + +#include <cstdint> +#include <vector> + +#include <libsnapshot/cow_writer.h> + +#include "update_engine/payload_consumer/extent_writer.h" +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { + +class SnapshotExtentWriter : public chromeos_update_engine::ExtentWriter { + public: + explicit SnapshotExtentWriter(android::snapshot::ICowWriter* cow_writer); + ~SnapshotExtentWriter(); + // Returns true on success. + bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents, + uint32_t block_size) override; + // Returns true on success. + // This will construct a COW_REPLACE operation and forward it to CowWriter. It + // is important that caller does not perform SOURCE_COPY operation on this + // class, otherwise raw data will be stored. Caller should find ways to use + // COW_COPY whenever possible. + bool Write(const void* bytes, size_t count) override; + + private: + bool next_extent(); + [[nodiscard]] size_t ConsumeWithBuffer(const uint8_t* bytes, size_t count); + // It's a non-owning pointer, because PartitionWriter owns the CowWruter. This + // allows us to use a single instance of CowWriter for all operations applied + // to the same partition. + android::snapshot::ICowWriter* cow_writer_; + google::protobuf::RepeatedPtrField<Extent> extents_; + size_t cur_extent_idx_; + std::vector<uint8_t> buffer_; + size_t block_size_; +}; + +} // namespace chromeos_update_engine + +#endif diff --git a/payload_consumer/snapshot_extent_writer_unittest.cc b/payload_consumer/snapshot_extent_writer_unittest.cc new file mode 100644 index 00000000..22010433 --- /dev/null +++ b/payload_consumer/snapshot_extent_writer_unittest.cc @@ -0,0 +1,180 @@ +// +// 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 <array> +#include <cstring> +#include <map> +#include <numeric> +#include <vector> + +#include <gtest/gtest.h> +#include <google/protobuf/message_lite.h> +#include <libsnapshot/cow_writer.h> + +#include "update_engine/payload_consumer/snapshot_extent_writer.h" +#include "update_engine/payload_generator/delta_diff_generator.h" +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { + +class FakeCowWriter : public android::snapshot::ICowWriter { + public: + struct CowOp { + enum { COW_COPY, COW_REPLACE, COW_ZERO } type; + std::vector<unsigned char> data; + union { + size_t source_block; + size_t num_blocks; + }; + }; + using ICowWriter::ICowWriter; + ~FakeCowWriter() = default; + + bool EmitCopy(uint64_t new_block, uint64_t old_block) override { + operations_[new_block] = {.type = CowOp::COW_COPY, + .source_block = static_cast<size_t>(old_block)}; + return true; + } + bool EmitRawBlocks(uint64_t new_block_start, + const void* data, + size_t size) override { + auto&& op = operations_[new_block_start]; + const auto uint8_ptr = static_cast<const unsigned char*>(data); + op.data.insert(op.data.end(), uint8_ptr, uint8_ptr + size); + return true; + } + bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override { + operations_[new_block_start] = {.type = CowOp::COW_ZERO}; + return true; + } + bool Finalize() override { + finalize_called_ = true; + return true; + } + + bool EmitLabel(uint64_t label) { + label_count_++; + return true; + } + + // Return number of bytes the cow image occupies on disk. + uint64_t GetCowSize() override { + return std::accumulate( + operations_.begin(), operations_.end(), 0, [](auto&& acc, auto&& op) { + return acc + op.second.data.size(); + }); + } + bool Contains(size_t block) { + return operations_.find(block) != operations_.end(); + } + bool finalize_called_ = true; + size_t label_count_ = 0; + std::map<size_t, CowOp> operations_; +}; + +class SnapshotExtentWriterTest : public ::testing::Test { + public: + void SetUp() override {} + + protected: + android::snapshot::CowOptions options_ = { + .block_size = static_cast<uint32_t>(kBlockSize)}; + FakeCowWriter cow_writer_{options_}; + SnapshotExtentWriter writer_{&cow_writer_}; +}; + +void AddExtent(google::protobuf::RepeatedPtrField<Extent>* extents, + size_t start_block, + size_t num_blocks) { + auto&& extent = extents->Add(); + extent->set_start_block(start_block); + extent->set_num_blocks(num_blocks); +} + +TEST_F(SnapshotExtentWriterTest, BufferWrites) { + google::protobuf::RepeatedPtrField<Extent> extents; + AddExtent(&extents, 123, 1); + writer_.Init(extents, kBlockSize); + + std::vector<uint8_t> buf(kBlockSize, 0); + buf[123] = 231; + buf[231] = 123; + buf[buf.size() - 1] = 255; + + writer_.Write(buf.data(), kBlockSize - 1); + ASSERT_TRUE(cow_writer_.operations_.empty()) + << "Haven't send data of a complete block yet, CowWriter should not be " + "invoked."; + writer_.Write(buf.data() + kBlockSize - 1, 1); + ASSERT_TRUE(cow_writer_.Contains(123)) + << "Once a block of data is sent to SnapshotExtentWriter, it should " + "forward data to cow_writer."; + ASSERT_EQ(cow_writer_.operations_.size(), 1U); + ASSERT_EQ(buf, cow_writer_.operations_[123].data); +} + +TEST_F(SnapshotExtentWriterTest, NonBufferedWrites) { + google::protobuf::RepeatedPtrField<Extent> extents; + AddExtent(&extents, 123, 1); + AddExtent(&extents, 125, 1); + writer_.Init(extents, kBlockSize); + + std::vector<uint8_t> buf(kBlockSize * 2, 0); + buf[123] = 231; + buf[231] = 123; + buf[buf.size() - 1] = 255; + + writer_.Write(buf.data(), buf.size()); + ASSERT_TRUE(cow_writer_.Contains(123)); + ASSERT_TRUE(cow_writer_.Contains(125)); + + ASSERT_EQ(cow_writer_.operations_.size(), 2U); + auto actual_data = cow_writer_.operations_[123].data; + actual_data.insert(actual_data.end(), + cow_writer_.operations_[125].data.begin(), + cow_writer_.operations_[125].data.end()); + ASSERT_EQ(buf, actual_data); +} + +TEST_F(SnapshotExtentWriterTest, WriteAcrossBlockBoundary) { + google::protobuf::RepeatedPtrField<Extent> extents; + AddExtent(&extents, 123, 1); + AddExtent(&extents, 125, 2); + writer_.Init(extents, kBlockSize); + + std::vector<uint8_t> buf(kBlockSize * 3); + std::memset(buf.data(), 0, buf.size()); + buf[123] = 231; + buf[231] = 123; + buf[buf.size() - 1] = 255; + buf[kBlockSize - 1] = 254; + + writer_.Write(buf.data(), kBlockSize - 1); + ASSERT_TRUE(cow_writer_.operations_.empty()) + << "Haven't send data of a complete block yet, CowWriter should not be " + "invoked."; + writer_.Write(buf.data() + kBlockSize - 1, 1 + kBlockSize * 2); + ASSERT_TRUE(cow_writer_.Contains(123)); + ASSERT_TRUE(cow_writer_.Contains(125)); + + ASSERT_EQ(cow_writer_.operations_.size(), 2U); + auto actual_data = cow_writer_.operations_[123].data; + actual_data.insert(actual_data.end(), + cow_writer_.operations_[125].data.begin(), + cow_writer_.operations_[125].data.end()); + ASSERT_EQ(buf, actual_data); +} +} // namespace chromeos_update_engine diff --git a/payload_consumer/vabc_partition_writer.cc b/payload_consumer/vabc_partition_writer.cc new file mode 100644 index 00000000..0843fffb --- /dev/null +++ b/payload_consumer/vabc_partition_writer.cc @@ -0,0 +1,175 @@ +// +// 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/vabc_partition_writer.h" + +#include <memory> +#include <string> +#include <vector> + +#include <libsnapshot/cow_writer.h> + +#include "update_engine/common/cow_operation_convert.h" +#include "update_engine/common/utils.h" +#include "update_engine/payload_consumer/extent_writer.h" +#include "update_engine/payload_consumer/file_descriptor.h" +#include "update_engine/payload_consumer/install_plan.h" +#include "update_engine/payload_consumer/partition_writer.h" +#include "update_engine/payload_consumer/snapshot_extent_writer.h" + +namespace chromeos_update_engine { +// Expected layout of COW file: +// === Beginning of Cow Image === +// All Source Copy Operations +// ========== Label 0 ========== +// Operation 0 in PartitionUpdate +// ========== Label 1 ========== +// Operation 1 in PartitionUpdate +// ========== label 2 ========== +// Operation 2 in PartitionUpdate +// ========== label 3 ========== +// . +// . +// . + +// When resuming, pass |next_op_index_| as label to +// |InitializeWithAppend|. +// For example, suppose we finished writing SOURCE_COPY, and we finished writing +// operation 2 completely. Update is suspended when we are half way through +// operation 3. +// |cnext_op_index_| would be 3, so we pass 3 as +// label to |InitializeWithAppend|. The CowWriter will retain all data before +// label 3, Which contains all operation 2's data, but none of operation 3's +// data. + +bool VABCPartitionWriter::Init(const InstallPlan* install_plan, + bool source_may_exist, + size_t next_op_index) { + TEST_AND_RETURN_FALSE(install_plan != nullptr); + TEST_AND_RETURN_FALSE( + OpenSourcePartition(install_plan->source_slot, source_may_exist)); + std::optional<std::string> source_path; + if (!install_part_.source_path.empty()) { + // TODO(zhangkelvin) Make |source_path| a std::optional<std::string> + source_path = install_part_.source_path; + } + cow_writer_ = dynamic_control_->OpenCowWriter( + install_part_.name, source_path, install_plan->is_resume); + TEST_AND_RETURN_FALSE(cow_writer_ != nullptr); + + // ===== Resume case handling code goes here ==== + // It is possible that the SOURCE_COPY are already written but + // |next_op_index_| is still 0. In this case we discard previously written + // SOURCE_COPY, and start over. + if (install_plan->is_resume && next_op_index > 0) { + LOG(INFO) << "Resuming update on partition `" + << partition_update_.partition_name() << "` op index " + << next_op_index; + TEST_AND_RETURN_FALSE(cow_writer_->InitializeAppend(next_op_index)); + return true; + } else { + TEST_AND_RETURN_FALSE(cow_writer_->Initialize()); + } + + // ============================================== + + // TODO(zhangkelvin) Rewrite this in C++20 coroutine once that's available. + auto converted = ConvertToCowOperations(partition_update_.operations(), + partition_update_.merge_operations()); + + WriteAllCowOps(block_size_, converted, cow_writer_.get(), source_fd_); + return true; +} + +bool VABCPartitionWriter::WriteAllCowOps( + size_t block_size, + const std::vector<CowOperation>& converted, + android::snapshot::ICowWriter* cow_writer, + FileDescriptorPtr source_fd) { + std::vector<uint8_t> buffer(block_size); + + for (const auto& cow_op : converted) { + switch (cow_op.op) { + case CowOperation::CowCopy: + if (cow_op.src_block == cow_op.dst_block) { + continue; + } + TEST_AND_RETURN_FALSE( + cow_writer->AddCopy(cow_op.dst_block, cow_op.src_block)); + break; + case CowOperation::CowReplace: + ssize_t bytes_read = 0; + TEST_AND_RETURN_FALSE(utils::ReadAll(source_fd, + buffer.data(), + block_size, + cow_op.src_block * block_size, + &bytes_read)); + if (bytes_read <= 0 || static_cast<size_t>(bytes_read) != block_size) { + LOG(ERROR) << "source_fd->Read failed: " << bytes_read; + return false; + } + TEST_AND_RETURN_FALSE(cow_writer->AddRawBlocks( + cow_op.dst_block, buffer.data(), block_size)); + break; + } + } + + return true; +} + +std::unique_ptr<ExtentWriter> VABCPartitionWriter::CreateBaseExtentWriter() { + return std::make_unique<SnapshotExtentWriter>(cow_writer_.get()); +} + +[[nodiscard]] bool VABCPartitionWriter::PerformZeroOrDiscardOperation( + const InstallOperation& operation) { + for (const auto& extent : operation.dst_extents()) { + TEST_AND_RETURN_FALSE( + cow_writer_->AddZeroBlocks(extent.start_block(), extent.num_blocks())); + } + return true; +} + +[[nodiscard]] bool VABCPartitionWriter::PerformSourceCopyOperation( + const InstallOperation& operation, ErrorCode* error) { + // TODO(zhangkelvin) Probably just ignore SOURCE_COPY? They should be taken + // care of during Init(); + return true; +} + +void VABCPartitionWriter::CheckpointUpdateProgress(size_t next_op_index) { + // No need to call fsync/sync, as CowWriter flushes after a label is added + // added. + // if cow_writer_ failed, that means Init() failed. This function shouldn't be + // called if Init() fails. + TEST_AND_RETURN(cow_writer_ != nullptr); + cow_writer_->AddLabel(next_op_index); +} + +[[nodiscard]] bool VABCPartitionWriter::FinishedInstallOps() { + // Add a hardcoded magic label to indicate end of all install ops. This label + // is needed by filesystem verification, don't remove. + TEST_AND_RETURN_FALSE(cow_writer_ != nullptr); + return cow_writer_->AddLabel(kEndOfInstallLabel); +} + +VABCPartitionWriter::~VABCPartitionWriter() { + if (cow_writer_) { + cow_writer_->Finalize(); + } +} + +} // namespace chromeos_update_engine diff --git a/payload_consumer/vabc_partition_writer.h b/payload_consumer/vabc_partition_writer.h new file mode 100644 index 00000000..7fb2a2c6 --- /dev/null +++ b/payload_consumer/vabc_partition_writer.h @@ -0,0 +1,63 @@ +// +// 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_VABC_PARTITION_WRITER_H_ +#define UPDATE_ENGINE_VABC_PARTITION_WRITER_H_ + +#include <memory> +#include <vector> + +#include <libsnapshot/snapshot_writer.h> + +#include "update_engine/common/cow_operation_convert.h" +#include "update_engine/payload_consumer/install_plan.h" +#include "update_engine/payload_consumer/partition_writer.h" + +namespace chromeos_update_engine { +class VABCPartitionWriter final : public PartitionWriter { + public: + using PartitionWriter::PartitionWriter; + [[nodiscard]] bool Init(const InstallPlan* install_plan, + bool source_may_exist, + size_t next_op_index) override; + ~VABCPartitionWriter() override; + + [[nodiscard]] std::unique_ptr<ExtentWriter> CreateBaseExtentWriter() override; + + // Only ZERO and SOURCE_COPY InstallOperations are treated special by VABC + // Partition Writer. These operations correspond to COW_ZERO and COW_COPY. All + // other operations just get converted to COW_REPLACE. + [[nodiscard]] bool PerformZeroOrDiscardOperation( + const InstallOperation& operation) override; + [[nodiscard]] bool PerformSourceCopyOperation( + const InstallOperation& operation, ErrorCode* error) override; + + void CheckpointUpdateProgress(size_t next_op_index) override; + + static bool WriteAllCowOps(size_t block_size, + const std::vector<CowOperation>& converted, + android::snapshot::ICowWriter* cow_writer, + FileDescriptorPtr source_fd); + + [[nodiscard]] bool FinishedInstallOps() override; + + private: + std::unique_ptr<android::snapshot::ISnapshotWriter> cow_writer_; +}; + +} // namespace chromeos_update_engine + +#endif diff --git a/payload_consumer/verity_writer_android.cc b/payload_consumer/verity_writer_android.cc index d5437b64..e2fab7dd 100644 --- a/payload_consumer/verity_writer_android.cc +++ b/payload_consumer/verity_writer_android.cc @@ -29,6 +29,8 @@ extern "C" { } #include "update_engine/common/utils.h" +#include "update_engine/payload_consumer/cached_file_descriptor.h" +#include "update_engine/payload_consumer/file_descriptor.h" namespace chromeos_update_engine { @@ -41,9 +43,6 @@ 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); @@ -65,58 +64,86 @@ bool VerityWriterAndroid::Init(const InstallPlan::Partition& partition) { return false; } } + total_offset_ = 0; return true; } -bool VerityWriterAndroid::Update(uint64_t offset, +bool VerityWriterAndroid::Update(const uint64_t offset, const uint8_t* buffer, size_t size) { + if (offset != total_offset_) { + LOG(ERROR) << "Sequential read expected, expected to read at: " + << total_offset_ << " actual read occurs at: " << offset; + return false; + } if (partition_->hash_tree_size != 0) { - uint64_t hash_tree_data_end = + const uint64_t hash_tree_data_end = partition_->hash_tree_data_offset + partition_->hash_tree_data_size; - uint64_t start_offset = std::max(offset, partition_->hash_tree_data_offset); - uint64_t end_offset = std::min(offset + size, hash_tree_data_end); + const uint64_t start_offset = + std::max(offset, partition_->hash_tree_data_offset); + if (offset + size > hash_tree_data_end) { + LOG(WARNING) + << "Reading past hash_tree_data_end, something is probably " + "wrong, might cause incorrect hash of partitions. offset: " + << offset << " size: " << size + << " hash_tree_data_end: " << hash_tree_data_end; + } + const uint64_t end_offset = std::min(offset + size, hash_tree_data_end); if (start_offset < end_offset) { TEST_AND_RETURN_FALSE(hash_tree_builder_->Update( buffer + start_offset - offset, end_offset - start_offset)); if (end_offset == hash_tree_data_end) { - // All hash tree data blocks has been hashed, write hash tree to disk. - int fd = HANDLE_EINTR(open(partition_->target_path.c_str(), O_WRONLY)); - if (fd < 0) { - PLOG(ERROR) << "Failed to open " << partition_->target_path - << " to write hash tree."; - return false; - } - ScopedFdCloser fd_closer(&fd); - - LOG(INFO) << "Writing verity hash tree to " << partition_->target_path; - TEST_AND_RETURN_FALSE(hash_tree_builder_->BuildHashTree()); - TEST_AND_RETURN_FALSE(hash_tree_builder_->WriteHashTreeToFd( - fd, partition_->hash_tree_offset)); - hash_tree_builder_.reset(); + LOG(INFO) + << "Read everything before hash tree. Ready to write hash tree."; } } } + total_offset_ += size; + + return true; +} + +bool VerityWriterAndroid::Finalize(FileDescriptorPtr read_fd, + FileDescriptorPtr write_fd) { + const auto hash_tree_data_end = + partition_->hash_tree_data_offset + partition_->hash_tree_data_size; + if (total_offset_ < hash_tree_data_end) { + LOG(ERROR) << "Read up to " << total_offset_ + << " when we are expecting to read everything " + "before " + << hash_tree_data_end; + return false; + } + // All hash tree data blocks has been hashed, write hash tree to disk. + LOG(INFO) << "Writing verity hash tree to " << partition_->target_path; + TEST_AND_RETURN_FALSE(hash_tree_builder_->BuildHashTree()); + TEST_AND_RETURN_FALSE_ERRNO( + write_fd->Seek(partition_->hash_tree_offset, SEEK_SET)); + auto success = + hash_tree_builder_->WriteHashTree([write_fd](auto data, auto size) { + return utils::WriteAll(write_fd, data, size); + }); + // hashtree builder already prints error messages. + TEST_AND_RETURN_FALSE(success); + hash_tree_builder_.reset(); if (partition_->fec_size != 0) { - uint64_t fec_data_end = - partition_->fec_data_offset + partition_->fec_data_size; - if (offset < fec_data_end && offset + size >= fec_data_end) { - LOG(INFO) << "Writing verity FEC to " << partition_->target_path; - TEST_AND_RETURN_FALSE(EncodeFEC(partition_->target_path, - partition_->fec_data_offset, - partition_->fec_data_size, - partition_->fec_offset, - partition_->fec_size, - partition_->fec_roots, - partition_->block_size, - false /* verify_mode */)); - } + LOG(INFO) << "Writing verity FEC to " << partition_->target_path; + TEST_AND_RETURN_FALSE(EncodeFEC(read_fd, + write_fd, + partition_->fec_data_offset, + partition_->fec_data_size, + partition_->fec_offset, + partition_->fec_size, + partition_->fec_roots, + partition_->block_size, + false /* verify_mode */)); } return true; } -bool VerityWriterAndroid::EncodeFEC(const std::string& path, +bool VerityWriterAndroid::EncodeFEC(FileDescriptorPtr read_fd, + FileDescriptorPtr write_fd, uint64_t data_offset, uint64_t data_size, uint64_t fec_offset, @@ -135,13 +162,10 @@ bool VerityWriterAndroid::EncodeFEC(const std::string& path, init_rs_char(FEC_PARAMS(fec_roots)), &free_rs_char); TEST_AND_RETURN_FALSE(rs_char != nullptr); - int fd = HANDLE_EINTR(open(path.c_str(), verify_mode ? O_RDONLY : O_RDWR)); - if (fd < 0) { - PLOG(ERROR) << "Failed to open " << path << " to write FEC."; - return false; - } - ScopedFdCloser fd_closer(&fd); - + // Cache at most 1MB of fec data, in VABC, we need to re-open fd if we + // perform a read() operation after write(). So reduce the number of writes + // can save unnecessary re-opens. + write_fd = std::make_shared<CachedFileDescriptor>(write_fd, 1 * (1 << 20)); for (size_t i = 0; i < rounds; i++) { // Encodes |block_size| number of rs blocks each round so that we can read // one block each time instead of 1 byte to increase random read @@ -154,13 +178,13 @@ bool VerityWriterAndroid::EncodeFEC(const std::string& path, // Don't read past |data_size|, treat them as 0. if (offset < data_size) { ssize_t bytes_read = 0; - TEST_AND_RETURN_FALSE(utils::PReadAll(fd, + TEST_AND_RETURN_FALSE(utils::PReadAll(read_fd, buffer.data(), buffer.size(), data_offset + offset, &bytes_read)); - TEST_AND_RETURN_FALSE(bytes_read == - static_cast<ssize_t>(buffer.size())); + TEST_AND_RETURN_FALSE(bytes_read >= 0); + TEST_AND_RETURN_FALSE(static_cast<size_t>(bytes_read) == buffer.size()); } for (size_t k = 0; k < buffer.size(); k++) { rs_blocks[k * rs_n + j] = buffer[k]; @@ -179,17 +203,43 @@ bool VerityWriterAndroid::EncodeFEC(const std::string& path, brillo::Blob fec_read(fec.size()); ssize_t bytes_read = 0; TEST_AND_RETURN_FALSE(utils::PReadAll( - fd, fec_read.data(), fec_read.size(), fec_offset, &bytes_read)); - TEST_AND_RETURN_FALSE(bytes_read == - static_cast<ssize_t>(fec_read.size())); + read_fd, fec_read.data(), fec_read.size(), fec_offset, &bytes_read)); + TEST_AND_RETURN_FALSE(bytes_read >= 0); + TEST_AND_RETURN_FALSE(static_cast<size_t>(bytes_read) == fec_read.size()); TEST_AND_RETURN_FALSE(fec == fec_read); } else { - TEST_AND_RETURN_FALSE( - utils::PWriteAll(fd, fec.data(), fec.size(), fec_offset)); + CHECK(write_fd); + write_fd->Seek(fec_offset, SEEK_SET); + if (!utils::WriteAll(write_fd, fec.data(), fec.size())) { + PLOG(ERROR) << "EncodeFEC write() failed"; + return false; + } } fec_offset += fec.size(); } - + write_fd->Flush(); return true; } + +bool VerityWriterAndroid::EncodeFEC(const std::string& path, + uint64_t data_offset, + uint64_t data_size, + uint64_t fec_offset, + uint64_t fec_size, + uint32_t fec_roots, + uint32_t block_size, + bool verify_mode) { + FileDescriptorPtr fd(new EintrSafeFileDescriptor()); + TEST_AND_RETURN_FALSE( + fd->Open(path.c_str(), verify_mode ? O_RDONLY : O_RDWR)); + return EncodeFEC(fd, + fd, + data_offset, + data_size, + fec_offset, + fec_size, + fec_roots, + block_size, + verify_mode); +} } // namespace chromeos_update_engine diff --git a/payload_consumer/verity_writer_android.h b/payload_consumer/verity_writer_android.h index 05a58566..8339528d 100644 --- a/payload_consumer/verity_writer_android.h +++ b/payload_consumer/verity_writer_android.h @@ -22,6 +22,7 @@ #include <verity/hash_tree_builder.h> +#include "payload_consumer/file_descriptor.h" #include "update_engine/payload_consumer/verity_writer_interface.h" namespace chromeos_update_engine { @@ -31,8 +32,9 @@ class VerityWriterAndroid : public VerityWriterInterface { VerityWriterAndroid() = default; ~VerityWriterAndroid() override = default; - bool Init(const InstallPlan::Partition& partition) override; + bool Init(const InstallPlan::Partition& partition); bool Update(uint64_t offset, const uint8_t* buffer, size_t size) override; + bool Finalize(FileDescriptorPtr read_fd, FileDescriptorPtr write_fd) override; // Read [data_offset : data_offset + data_size) from |path| and encode FEC // data, if |verify_mode|, then compare the encoded FEC with the one in @@ -40,6 +42,15 @@ class VerityWriterAndroid : public VerityWriterInterface { // in each Update() like hash tree, because for every rs block, its data are // spreaded across entire |data_size|, unless we can cache all data in // memory, we have to re-read them from disk. + static bool EncodeFEC(FileDescriptorPtr read_fd, + FileDescriptorPtr write_fd, + uint64_t data_offset, + uint64_t data_size, + uint64_t fec_offset, + uint64_t fec_size, + uint32_t fec_roots, + uint32_t block_size, + bool verify_mode); static bool EncodeFEC(const std::string& path, uint64_t data_offset, uint64_t data_size, @@ -53,7 +64,7 @@ class VerityWriterAndroid : public VerityWriterInterface { const InstallPlan::Partition* partition_ = nullptr; std::unique_ptr<HashTreeBuilder> hash_tree_builder_; - + uint64_t total_offset_ = 0; DISALLOW_COPY_AND_ASSIGN(VerityWriterAndroid); }; diff --git a/payload_consumer/verity_writer_android_unittest.cc b/payload_consumer/verity_writer_android_unittest.cc index f943ce8e..75da0ae3 100644 --- a/payload_consumer/verity_writer_android_unittest.cc +++ b/payload_consumer/verity_writer_android_unittest.cc @@ -16,11 +16,14 @@ #include "update_engine/payload_consumer/verity_writer_android.h" +#include <fcntl.h> + #include <brillo/secure_blob.h> #include <gtest/gtest.h> #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" +#include "update_engine/payload_consumer/file_descriptor.h" namespace chromeos_update_engine { @@ -35,19 +38,23 @@ class VerityWriterAndroidTest : public ::testing::Test { partition_.hash_tree_size = 4096; partition_.hash_tree_algorithm = "sha1"; partition_.fec_roots = 2; + partition_fd_ = std::make_shared<EintrSafeFileDescriptor>(); + partition_fd_->Open(partition_.target_path.c_str(), O_RDWR); } VerityWriterAndroid verity_writer_; InstallPlan::Partition partition_; - test_utils::ScopedTempFile temp_file_; + FileDescriptorPtr partition_fd_; + ScopedTempFile temp_file_; }; TEST_F(VerityWriterAndroidTest, SimpleTest) { brillo::Blob part_data(8192); test_utils::WriteFileVector(partition_.target_path, part_data); ASSERT_TRUE(verity_writer_.Init(partition_)); - EXPECT_TRUE(verity_writer_.Update(0, part_data.data(), 4096)); - EXPECT_TRUE(verity_writer_.Update(4096, part_data.data() + 4096, 4096)); + ASSERT_TRUE(verity_writer_.Update(0, part_data.data(), 4096)); + ASSERT_TRUE(verity_writer_.Update(4096, part_data.data() + 4096, 4096)); + ASSERT_TRUE(verity_writer_.Finalize(partition_fd_, partition_fd_)); brillo::Blob actual_part; utils::ReadFile(partition_.target_path, &actual_part); // dd if=/dev/zero bs=4096 count=1 2>/dev/null | sha1sum | xxd -r -p | @@ -56,7 +63,7 @@ TEST_F(VerityWriterAndroidTest, SimpleTest) { 0x1d, 0xf3, 0xbf, 0xb2, 0x6b, 0x4f, 0xb7, 0xcd, 0x95, 0xfb, 0x7b, 0xff, 0x1d}; memcpy(part_data.data() + 4096, hash.data(), hash.size()); - EXPECT_EQ(part_data, actual_part); + ASSERT_EQ(part_data, actual_part); } TEST_F(VerityWriterAndroidTest, NoOpTest) { @@ -64,19 +71,28 @@ TEST_F(VerityWriterAndroidTest, NoOpTest) { partition_.hash_tree_size = 0; brillo::Blob part_data(4096); ASSERT_TRUE(verity_writer_.Init(partition_)); - EXPECT_TRUE(verity_writer_.Update(0, part_data.data(), part_data.size())); - EXPECT_TRUE(verity_writer_.Update(4096, part_data.data(), part_data.size())); - EXPECT_TRUE(verity_writer_.Update(8192, part_data.data(), part_data.size())); + ASSERT_TRUE(verity_writer_.Update(0, part_data.data(), part_data.size())); + ASSERT_TRUE(verity_writer_.Update(4096, part_data.data(), part_data.size())); + ASSERT_TRUE(verity_writer_.Update(8192, part_data.data(), part_data.size())); +} + +TEST_F(VerityWriterAndroidTest, DiscontinuedRead) { + partition_.hash_tree_data_size = 8192; + partition_.hash_tree_size = 4096; + brillo::Blob part_data(4096); + ASSERT_TRUE(verity_writer_.Init(partition_)); + ASSERT_TRUE(verity_writer_.Update(0, part_data.data(), part_data.size())); + ASSERT_FALSE(verity_writer_.Update(8192, part_data.data(), part_data.size())); } TEST_F(VerityWriterAndroidTest, InvalidHashAlgorithmTest) { partition_.hash_tree_algorithm = "sha123"; - EXPECT_FALSE(verity_writer_.Init(partition_)); + ASSERT_FALSE(verity_writer_.Init(partition_)); } TEST_F(VerityWriterAndroidTest, WrongHashTreeSizeTest) { partition_.hash_tree_size = 8192; - EXPECT_FALSE(verity_writer_.Init(partition_)); + ASSERT_FALSE(verity_writer_.Init(partition_)); } TEST_F(VerityWriterAndroidTest, SHA256Test) { @@ -84,8 +100,9 @@ TEST_F(VerityWriterAndroidTest, SHA256Test) { brillo::Blob part_data(8192); test_utils::WriteFileVector(partition_.target_path, part_data); ASSERT_TRUE(verity_writer_.Init(partition_)); - EXPECT_TRUE(verity_writer_.Update(0, part_data.data(), 4096)); - EXPECT_TRUE(verity_writer_.Update(4096, part_data.data() + 4096, 4096)); + ASSERT_TRUE(verity_writer_.Update(0, part_data.data(), 4096)); + ASSERT_TRUE(verity_writer_.Update(4096, part_data.data() + 4096, 4096)); + ASSERT_TRUE(verity_writer_.Finalize(partition_fd_, partition_fd_)); brillo::Blob actual_part; utils::ReadFile(partition_.target_path, &actual_part); // dd if=/dev/zero bs=4096 count=1 2>/dev/null | sha256sum | xxd -r -p | @@ -95,7 +112,33 @@ TEST_F(VerityWriterAndroidTest, SHA256Test) { 0x4f, 0x58, 0x05, 0xff, 0x7c, 0xb4, 0x7c, 0x7a, 0x85, 0xda, 0xbd, 0x8b, 0x48, 0x89, 0x2c, 0xa7}; memcpy(part_data.data() + 4096, hash.data(), hash.size()); - EXPECT_EQ(part_data, actual_part); + ASSERT_EQ(part_data, actual_part); +} + +TEST_F(VerityWriterAndroidTest, NonZeroOffsetSHA256Test) { + partition_.hash_tree_algorithm = "sha256"; + partition_.hash_tree_data_offset = 100; + partition_.hash_tree_offset = + partition_.hash_tree_data_offset + partition_.hash_tree_data_size; + brillo::Blob part_data(8192 + partition_.hash_tree_data_offset); + test_utils::WriteFileVector(partition_.target_path, part_data); + ASSERT_TRUE(verity_writer_.Init(partition_)); + ASSERT_TRUE(verity_writer_.Update(0, part_data.data(), 4096)); + ASSERT_TRUE(verity_writer_.Update(4096, part_data.data() + 4096, 4096)); + ASSERT_TRUE(verity_writer_.Update( + 8192, part_data.data() + 8192, partition_.hash_tree_data_offset)); + ASSERT_TRUE(verity_writer_.Finalize(partition_fd_, partition_fd_)); + brillo::Blob actual_part; + utils::ReadFile(partition_.target_path, &actual_part); + // dd if=/dev/zero bs=4096 count=1 2>/dev/null | sha256sum | xxd -r -p | + // hexdump -v -e '/1 "0x%02x, "' + brillo::Blob hash = {0xad, 0x7f, 0xac, 0xb2, 0x58, 0x6f, 0xc6, 0xe9, + 0x66, 0xc0, 0x04, 0xd7, 0xd1, 0xd1, 0x6b, 0x02, + 0x4f, 0x58, 0x05, 0xff, 0x7c, 0xb4, 0x7c, 0x7a, + 0x85, 0xda, 0xbd, 0x8b, 0x48, 0x89, 0x2c, 0xa7}; + memcpy( + part_data.data() + partition_.hash_tree_offset, hash.data(), hash.size()); + ASSERT_EQ(part_data, actual_part); } TEST_F(VerityWriterAndroidTest, FECTest) { @@ -106,7 +149,8 @@ TEST_F(VerityWriterAndroidTest, FECTest) { brillo::Blob part_data(3 * 4096, 0x1); test_utils::WriteFileVector(partition_.target_path, part_data); ASSERT_TRUE(verity_writer_.Init(partition_)); - EXPECT_TRUE(verity_writer_.Update(0, part_data.data(), part_data.size())); + ASSERT_TRUE(verity_writer_.Update(0, part_data.data(), part_data.size())); + ASSERT_TRUE(verity_writer_.Finalize(partition_fd_, partition_fd_)); brillo::Blob actual_part; utils::ReadFile(partition_.target_path, &actual_part); // Write FEC data. @@ -114,7 +158,7 @@ TEST_F(VerityWriterAndroidTest, FECTest) { part_data[i] = 0x8e; part_data[i + 1] = 0x8f; } - EXPECT_EQ(part_data, actual_part); + ASSERT_EQ(part_data, actual_part); } } // namespace chromeos_update_engine diff --git a/payload_consumer/verity_writer_interface.h b/payload_consumer/verity_writer_interface.h index a3ecef3c..37ed605c 100644 --- a/payload_consumer/verity_writer_interface.h +++ b/payload_consumer/verity_writer_interface.h @@ -22,6 +22,7 @@ #include <base/macros.h> +#include "payload_consumer/file_descriptor.h" #include "update_engine/payload_consumer/install_plan.h" namespace chromeos_update_engine { @@ -37,6 +38,10 @@ class VerityWriterInterface { // blocks has passed. virtual bool Update(uint64_t offset, const uint8_t* buffer, size_t size) = 0; + // Write hash tree && FEC data to underlying fd, if they are present + virtual bool Finalize(FileDescriptorPtr read_fd, + FileDescriptorPtr write_fd) = 0; + protected: VerityWriterInterface() = default; diff --git a/payload_consumer/verity_writer_stub.cc b/payload_consumer/verity_writer_stub.cc index a0e24673..8bff076f 100644 --- a/payload_consumer/verity_writer_stub.cc +++ b/payload_consumer/verity_writer_stub.cc @@ -36,4 +36,9 @@ bool VerityWriterStub::Update(uint64_t offset, return true; } +bool VerityWriterStub::Finalize(FileDescriptorPtr read_fd, + FileDescriptorPtr write_fd) { + return true; +} + } // namespace chromeos_update_engine diff --git a/payload_consumer/verity_writer_stub.h b/payload_consumer/verity_writer_stub.h index ea5e5749..a20db036 100644 --- a/payload_consumer/verity_writer_stub.h +++ b/payload_consumer/verity_writer_stub.h @@ -28,6 +28,7 @@ class VerityWriterStub : public VerityWriterInterface { bool Init(const InstallPlan::Partition& partition) override; bool Update(uint64_t offset, const uint8_t* buffer, size_t size) override; + bool Finalize(FileDescriptorPtr read_fd, FileDescriptorPtr write_fd) override; private: DISALLOW_COPY_AND_ASSIGN(VerityWriterStub); diff --git a/payload_consumer/xz_extent_writer.cc b/payload_consumer/xz_extent_writer.cc index a5b939db..a6483519 100644 --- a/payload_consumer/xz_extent_writer.cc +++ b/payload_consumer/xz_extent_writer.cc @@ -57,12 +57,11 @@ XzExtentWriter::~XzExtentWriter() { TEST_AND_RETURN(input_buffer_.empty()); } -bool XzExtentWriter::Init(FileDescriptorPtr fd, - const RepeatedPtrField<Extent>& extents, +bool XzExtentWriter::Init(const RepeatedPtrField<Extent>& extents, uint32_t block_size) { stream_ = xz_dec_init(XZ_DYNALLOC, kXzMaxDictSize); TEST_AND_RETURN_FALSE(stream_ != nullptr); - return underlying_writer_->Init(fd, extents, block_size); + return underlying_writer_->Init(extents, block_size); } bool XzExtentWriter::Write(const void* bytes, size_t count) { diff --git a/payload_consumer/xz_extent_writer.h b/payload_consumer/xz_extent_writer.h index e022274e..70338f20 100644 --- a/payload_consumer/xz_extent_writer.h +++ b/payload_consumer/xz_extent_writer.h @@ -39,8 +39,7 @@ class XzExtentWriter : public ExtentWriter { : underlying_writer_(std::move(underlying_writer)) {} ~XzExtentWriter() override; - bool Init(FileDescriptorPtr fd, - const google::protobuf::RepeatedPtrField<Extent>& extents, + bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents, uint32_t block_size) override; bool Write(const void* bytes, size_t count) override; diff --git a/payload_consumer/xz_extent_writer_unittest.cc b/payload_consumer/xz_extent_writer_unittest.cc index 34980a9a..5269dbc9 100644 --- a/payload_consumer/xz_extent_writer_unittest.cc +++ b/payload_consumer/xz_extent_writer_unittest.cc @@ -87,7 +87,7 @@ class XzExtentWriterTest : public ::testing::Test { } void WriteAll(const brillo::Blob& compressed) { - EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024)); + EXPECT_TRUE(xz_writer_->Init({}, 1024)); EXPECT_TRUE(xz_writer_->Write(compressed.data(), compressed.size())); EXPECT_TRUE(fake_extent_writer_->InitCalled()); @@ -130,7 +130,7 @@ TEST_F(XzExtentWriterTest, CompressedDataBiggerThanTheBuffer) { } TEST_F(XzExtentWriterTest, GarbageDataRejected) { - EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024)); + EXPECT_TRUE(xz_writer_->Init({}, 1024)); // The sample_data_ is an uncompressed string. EXPECT_FALSE(xz_writer_->Write(sample_data_.data(), sample_data_.size())); } @@ -138,7 +138,7 @@ TEST_F(XzExtentWriterTest, GarbageDataRejected) { TEST_F(XzExtentWriterTest, PartialDataIsKept) { brillo::Blob compressed(std::begin(kCompressed30KiBofA), std::end(kCompressed30KiBofA)); - EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024)); + EXPECT_TRUE(xz_writer_->Init({}, 1024)); for (uint8_t byte : compressed) { EXPECT_TRUE(xz_writer_->Write(&byte, 1)); } diff --git a/payload_generator/ab_generator_unittest.cc b/payload_generator/ab_generator_unittest.cc index 270657ab..84eeb77b 100644 --- a/payload_generator/ab_generator_unittest.cc +++ b/payload_generator/ab_generator_unittest.cc @@ -30,10 +30,10 @@ #include "update_engine/common/test_utils.h" #include "update_engine/common/utils.h" #include "update_engine/payload_generator/annotated_operation.h" -#include "update_engine/payload_generator/bzip.h" #include "update_engine/payload_generator/delta_diff_generator.h" #include "update_engine/payload_generator/extent_ranges.h" #include "update_engine/payload_generator/extent_utils.h" +#include "update_engine/payload_generator/xz.h" using std::string; using std::vector; @@ -48,8 +48,8 @@ bool ExtentEquals(const Extent& ext, return ext.start_block() == start_block && ext.num_blocks() == num_blocks; } -// Tests splitting of a REPLACE/REPLACE_BZ operation. -void TestSplitReplaceOrReplaceBzOperation(InstallOperation::Type orig_type, +// Tests splitting of a REPLACE/REPLACE_XZ operation. +void TestSplitReplaceOrReplaceXzOperation(InstallOperation::Type orig_type, bool compressible) { const size_t op_ex1_start_block = 2; const size_t op_ex1_num_blocks = 2; @@ -70,8 +70,7 @@ void TestSplitReplaceOrReplaceBzOperation(InstallOperation::Type orig_type, part_data.push_back(dis(gen)); } ASSERT_EQ(part_size, part_data.size()); - test_utils::ScopedTempFile part_file( - "SplitReplaceOrReplaceBzTest_part.XXXXXX"); + ScopedTempFile part_file("SplitReplaceOrReplaceXzTest_part.XXXXXX"); ASSERT_TRUE(test_utils::WriteFileVector(part_file.path(), part_data)); // Create original operation and blob data. @@ -97,7 +96,7 @@ void TestSplitReplaceOrReplaceBzOperation(InstallOperation::Type orig_type, if (orig_type == InstallOperation::REPLACE) { op_blob = op_data; } else { - ASSERT_TRUE(BzipCompress(op_data, &op_blob)); + ASSERT_TRUE(XzCompress(op_data, &op_blob)); } op.set_data_offset(0); op.set_data_length(op_blob.size()); @@ -107,8 +106,7 @@ void TestSplitReplaceOrReplaceBzOperation(InstallOperation::Type orig_type, aop.name = "SplitTestOp"; // Create the data file. - test_utils::ScopedTempFile data_file( - "SplitReplaceOrReplaceBzTest_data.XXXXXX"); + ScopedTempFile data_file("SplitReplaceOrReplaceXzTest_data.XXXXXX"); EXPECT_TRUE(test_utils::WriteFileVector(data_file.path(), op_blob)); int data_fd = open(data_file.path().c_str(), O_RDWR, 000); EXPECT_GE(data_fd, 0); @@ -118,14 +116,14 @@ void TestSplitReplaceOrReplaceBzOperation(InstallOperation::Type orig_type, // Split the operation. vector<AnnotatedOperation> result_ops; - PayloadVersion version(kChromeOSMajorPayloadVersion, + PayloadVersion version(kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion); ASSERT_TRUE(ABGenerator::SplitAReplaceOp( version, aop, part_file.path(), &result_ops, &blob_file)); // Check the result. InstallOperation::Type expected_type = - compressible ? InstallOperation::REPLACE_BZ : InstallOperation::REPLACE; + compressible ? InstallOperation::REPLACE_XZ : InstallOperation::REPLACE; ASSERT_EQ(2U, result_ops.size()); @@ -143,7 +141,7 @@ void TestSplitReplaceOrReplaceBzOperation(InstallOperation::Type orig_type, part_data.begin() + op_ex1_offset + op_ex1_size); brillo::Blob first_expected_blob; if (compressible) { - ASSERT_TRUE(BzipCompress(first_expected_data, &first_expected_blob)); + ASSERT_TRUE(XzCompress(first_expected_data, &first_expected_blob)); } else { first_expected_blob = first_expected_data; } @@ -173,7 +171,7 @@ void TestSplitReplaceOrReplaceBzOperation(InstallOperation::Type orig_type, part_data.begin() + op_ex2_offset + op_ex2_size); brillo::Blob second_expected_blob; if (compressible) { - ASSERT_TRUE(BzipCompress(second_expected_data, &second_expected_blob)); + ASSERT_TRUE(XzCompress(second_expected_data, &second_expected_blob)); } else { second_expected_blob = second_expected_data; } @@ -199,8 +197,8 @@ void TestSplitReplaceOrReplaceBzOperation(InstallOperation::Type orig_type, } } -// Tests merging of REPLACE/REPLACE_BZ operations. -void TestMergeReplaceOrReplaceBzOperations(InstallOperation::Type orig_type, +// Tests merging of REPLACE/REPLACE_XZ operations. +void TestMergeReplaceOrReplaceXzOperations(InstallOperation::Type orig_type, bool compressible) { const size_t first_op_num_blocks = 1; const size_t second_op_num_blocks = 2; @@ -220,8 +218,7 @@ void TestMergeReplaceOrReplaceBzOperations(InstallOperation::Type orig_type, part_data.push_back(dis(gen)); } ASSERT_EQ(part_size, part_data.size()); - test_utils::ScopedTempFile part_file( - "MergeReplaceOrReplaceBzTest_part.XXXXXX"); + ScopedTempFile part_file("MergeReplaceOrReplaceXzTest_part.XXXXXX"); ASSERT_TRUE(test_utils::WriteFileVector(part_file.path(), part_data)); // Create original operations and blob data. @@ -239,7 +236,7 @@ void TestMergeReplaceOrReplaceBzOperations(InstallOperation::Type orig_type, if (orig_type == InstallOperation::REPLACE) { first_op_blob = first_op_data; } else { - ASSERT_TRUE(BzipCompress(first_op_data, &first_op_blob)); + ASSERT_TRUE(XzCompress(first_op_data, &first_op_blob)); } first_op.set_data_offset(0); first_op.set_data_length(first_op_blob.size()); @@ -259,7 +256,7 @@ void TestMergeReplaceOrReplaceBzOperations(InstallOperation::Type orig_type, if (orig_type == InstallOperation::REPLACE) { second_op_blob = second_op_data; } else { - ASSERT_TRUE(BzipCompress(second_op_data, &second_op_blob)); + ASSERT_TRUE(XzCompress(second_op_data, &second_op_blob)); } second_op.set_data_offset(first_op_blob.size()); second_op.set_data_length(second_op_blob.size()); @@ -271,8 +268,7 @@ void TestMergeReplaceOrReplaceBzOperations(InstallOperation::Type orig_type, aops.push_back(second_aop); // Create the data file. - test_utils::ScopedTempFile data_file( - "MergeReplaceOrReplaceBzTest_data.XXXXXX"); + ScopedTempFile data_file("MergeReplaceOrReplaceXzTest_data.XXXXXX"); EXPECT_TRUE(test_utils::WriteFileVector(data_file.path(), blob_data)); int data_fd = open(data_file.path().c_str(), O_RDWR, 000); EXPECT_GE(data_fd, 0); @@ -281,14 +277,14 @@ void TestMergeReplaceOrReplaceBzOperations(InstallOperation::Type orig_type, BlobFileWriter blob_file(data_fd, &data_file_size); // Merge the operations. - PayloadVersion version(kChromeOSMajorPayloadVersion, + PayloadVersion version(kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion); EXPECT_TRUE(ABGenerator::MergeOperations( &aops, version, 5, part_file.path(), &blob_file)); // Check the result. InstallOperation::Type expected_op_type = - compressible ? InstallOperation::REPLACE_BZ : InstallOperation::REPLACE; + compressible ? InstallOperation::REPLACE_XZ : InstallOperation::REPLACE; EXPECT_EQ(1U, aops.size()); InstallOperation new_op = aops[0].op; EXPECT_EQ(expected_op_type, new_op.type()); @@ -303,7 +299,7 @@ void TestMergeReplaceOrReplaceBzOperations(InstallOperation::Type orig_type, part_data.begin() + total_op_size); brillo::Blob expected_blob; if (compressible) { - ASSERT_TRUE(BzipCompress(expected_data, &expected_blob)); + ASSERT_TRUE(XzCompress(expected_data, &expected_blob)); } else { expected_blob = expected_data; } @@ -384,19 +380,19 @@ TEST_F(ABGeneratorTest, SplitSourceCopyTest) { } TEST_F(ABGeneratorTest, SplitReplaceTest) { - TestSplitReplaceOrReplaceBzOperation(InstallOperation::REPLACE, false); + TestSplitReplaceOrReplaceXzOperation(InstallOperation::REPLACE, false); } -TEST_F(ABGeneratorTest, SplitReplaceIntoReplaceBzTest) { - TestSplitReplaceOrReplaceBzOperation(InstallOperation::REPLACE, true); +TEST_F(ABGeneratorTest, SplitReplaceIntoReplaceXzTest) { + TestSplitReplaceOrReplaceXzOperation(InstallOperation::REPLACE, true); } -TEST_F(ABGeneratorTest, SplitReplaceBzTest) { - TestSplitReplaceOrReplaceBzOperation(InstallOperation::REPLACE_BZ, true); +TEST_F(ABGeneratorTest, SplitReplaceXzTest) { + TestSplitReplaceOrReplaceXzOperation(InstallOperation::REPLACE_XZ, true); } -TEST_F(ABGeneratorTest, SplitReplaceBzIntoReplaceTest) { - TestSplitReplaceOrReplaceBzOperation(InstallOperation::REPLACE_BZ, false); +TEST_F(ABGeneratorTest, SplitReplaceXzIntoReplaceTest) { + TestSplitReplaceOrReplaceXzOperation(InstallOperation::REPLACE_XZ, false); } TEST_F(ABGeneratorTest, SortOperationsByDestinationTest) { @@ -464,7 +460,7 @@ TEST_F(ABGeneratorTest, MergeSourceCopyOperationsTest) { aops.push_back(third_aop); BlobFileWriter blob_file(0, nullptr); - PayloadVersion version(kChromeOSMajorPayloadVersion, + PayloadVersion version(kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion); EXPECT_TRUE(ABGenerator::MergeOperations(&aops, version, 5, "", &blob_file)); @@ -484,19 +480,19 @@ TEST_F(ABGeneratorTest, MergeSourceCopyOperationsTest) { } TEST_F(ABGeneratorTest, MergeReplaceOperationsTest) { - TestMergeReplaceOrReplaceBzOperations(InstallOperation::REPLACE, false); + TestMergeReplaceOrReplaceXzOperations(InstallOperation::REPLACE, false); } -TEST_F(ABGeneratorTest, MergeReplaceOperationsToReplaceBzTest) { - TestMergeReplaceOrReplaceBzOperations(InstallOperation::REPLACE, true); +TEST_F(ABGeneratorTest, MergeReplaceOperationsToReplaceXzTest) { + TestMergeReplaceOrReplaceXzOperations(InstallOperation::REPLACE, true); } -TEST_F(ABGeneratorTest, MergeReplaceBzOperationsTest) { - TestMergeReplaceOrReplaceBzOperations(InstallOperation::REPLACE_BZ, true); +TEST_F(ABGeneratorTest, MergeReplaceXzOperationsTest) { + TestMergeReplaceOrReplaceXzOperations(InstallOperation::REPLACE_XZ, true); } -TEST_F(ABGeneratorTest, MergeReplaceBzOperationsToReplaceTest) { - TestMergeReplaceOrReplaceBzOperations(InstallOperation::REPLACE_BZ, false); +TEST_F(ABGeneratorTest, MergeReplaceXzOperationsToReplaceTest) { + TestMergeReplaceOrReplaceXzOperations(InstallOperation::REPLACE_XZ, false); } TEST_F(ABGeneratorTest, NoMergeOperationsTest) { @@ -537,7 +533,7 @@ TEST_F(ABGeneratorTest, NoMergeOperationsTest) { aops.push_back(fourth_aop); BlobFileWriter blob_file(0, nullptr); - PayloadVersion version(kChromeOSMajorPayloadVersion, + PayloadVersion version(kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion); EXPECT_TRUE(ABGenerator::MergeOperations(&aops, version, 4, "", &blob_file)); @@ -561,7 +557,7 @@ TEST_F(ABGeneratorTest, AddSourceHashTest) { second_aop.op = second_op; aops.push_back(second_aop); - test_utils::ScopedTempFile src_part_file("AddSourceHashTest_src_part.XXXXXX"); + ScopedTempFile src_part_file("AddSourceHashTest_src_part.XXXXXX"); brillo::Blob src_data(kBlockSize); test_utils::FillWithData(&src_data); ASSERT_TRUE(test_utils::WriteFileVector(src_part_file.path(), src_data)); 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/blob_file_writer_unittest.cc b/payload_generator/blob_file_writer_unittest.cc index 487bc737..f4dcafb4 100644 --- a/payload_generator/blob_file_writer_unittest.cc +++ b/payload_generator/blob_file_writer_unittest.cc @@ -31,24 +31,21 @@ namespace chromeos_update_engine { class BlobFileWriterTest : public ::testing::Test {}; TEST(BlobFileWriterTest, SimpleTest) { - string blob_path; - int blob_fd; - EXPECT_TRUE( - utils::MakeTempFile("BlobFileWriterTest.XXXXXX", &blob_path, &blob_fd)); + ScopedTempFile blob_file("BlobFileWriterTest.XXXXXX", true); off_t blob_file_size = 0; - BlobFileWriter blob_file(blob_fd, &blob_file_size); + BlobFileWriter blob_file_writer(blob_file.fd(), &blob_file_size); - off_t blob_size = 1024; - brillo::Blob blob(blob_size); + const off_t kBlobSize = 1024; + brillo::Blob blob(kBlobSize); FillWithData(&blob); - EXPECT_EQ(0, blob_file.StoreBlob(blob)); - EXPECT_EQ(blob_size, blob_file.StoreBlob(blob)); + EXPECT_EQ(0, blob_file_writer.StoreBlob(blob)); + EXPECT_EQ(kBlobSize, blob_file_writer.StoreBlob(blob)); - brillo::Blob stored_blob(blob_size); + brillo::Blob stored_blob(kBlobSize); ssize_t bytes_read; - ASSERT_TRUE( - utils::PReadAll(blob_fd, stored_blob.data(), blob_size, 0, &bytes_read)); - EXPECT_EQ(bytes_read, blob_size); + ASSERT_TRUE(utils::PReadAll( + blob_file.fd(), stored_blob.data(), kBlobSize, 0, &bytes_read)); + EXPECT_EQ(bytes_read, kBlobSize); EXPECT_EQ(blob, stored_blob); } diff --git a/payload_generator/block_mapping_unittest.cc b/payload_generator/block_mapping_unittest.cc index 9b9b4f16..017548a9 100644 --- a/payload_generator/block_mapping_unittest.cc +++ b/payload_generator/block_mapping_unittest.cc @@ -36,8 +36,8 @@ namespace chromeos_update_engine { class BlockMappingTest : public ::testing::Test { protected: // Old new partition files used in testing. - test_utils::ScopedTempFile old_part_{"BlockMappingTest_old.XXXXXX"}; - test_utils::ScopedTempFile new_part_{"BlockMappingTest_new.XXXXXX"}; + ScopedTempFile old_part_{"BlockMappingTest_old.XXXXXX"}; + ScopedTempFile new_part_{"BlockMappingTest_new.XXXXXX"}; size_t block_size_{1024}; BlockMapping bm_{block_size_}; // BlockMapping under test. diff --git a/payload_generator/boot_img_filesystem_unittest.cc b/payload_generator/boot_img_filesystem_unittest.cc index 0b115e02..7805156f 100644 --- a/payload_generator/boot_img_filesystem_unittest.cc +++ b/payload_generator/boot_img_filesystem_unittest.cc @@ -63,7 +63,7 @@ class BootImgFilesystemTest : public ::testing::Test { return boot_img; } - test_utils::ScopedTempFile boot_file_; + ScopedTempFile boot_file_; }; TEST_F(BootImgFilesystemTest, SimpleTest) { diff --git a/payload_generator/cow_size_estimator.cc b/payload_generator/cow_size_estimator.cc new file mode 100644 index 00000000..01e99653 --- /dev/null +++ b/payload_generator/cow_size_estimator.cc @@ -0,0 +1,167 @@ +// +// 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/cow_size_estimator.h" + +#include <string> +#include <utility> +#include <vector> + +#include <android-base/unique_fd.h> +#include <libsnapshot/cow_writer.h> + +#include "update_engine/common/cow_operation_convert.h" +#include "update_engine/common/utils.h" +#include "update_engine/update_metadata.pb.h" + +namespace chromeos_update_engine { +using android::snapshot::CowWriter; + +namespace { +bool PerformReplaceOp(const InstallOperation& op, + CowWriter* writer, + FileDescriptorPtr target_fd, + size_t block_size) { + std::vector<unsigned char> buffer; + for (const auto& extent : op.dst_extents()) { + buffer.resize(extent.num_blocks() * block_size); + // No need to read from payload.bin then decompress, just read from target + // directly. + ssize_t bytes_read = 0; + auto success = utils::ReadAll(target_fd, + buffer.data(), + buffer.size(), + extent.start_block() * block_size, + &bytes_read); + TEST_AND_RETURN_FALSE(success); + CHECK_EQ(static_cast<size_t>(bytes_read), buffer.size()); + TEST_AND_RETURN_FALSE(writer->AddRawBlocks( + extent.start_block(), buffer.data(), buffer.size())); + } + return true; +} + +bool PerformZeroOp(const InstallOperation& op, + CowWriter* writer, + size_t block_size) { + for (const auto& extent : op.dst_extents()) { + TEST_AND_RETURN_FALSE( + writer->AddZeroBlocks(extent.start_block(), extent.num_blocks())); + } + return true; +} + +bool WriteAllCowOps(size_t block_size, + const std::vector<CowOperation>& converted, + android::snapshot::ICowWriter* cow_writer, + FileDescriptorPtr target_fd) { + std::vector<uint8_t> buffer(block_size); + + for (const auto& cow_op : converted) { + switch (cow_op.op) { + case CowOperation::CowCopy: + if (cow_op.src_block == cow_op.dst_block) { + continue; + } + TEST_AND_RETURN_FALSE( + cow_writer->AddCopy(cow_op.dst_block, cow_op.src_block)); + break; + case CowOperation::CowReplace: + ssize_t bytes_read = 0; + TEST_AND_RETURN_FALSE(chromeos_update_engine::utils::ReadAll( + target_fd, + buffer.data(), + block_size, + cow_op.dst_block * block_size, + &bytes_read)); + if (bytes_read <= 0 || static_cast<size_t>(bytes_read) != block_size) { + LOG(ERROR) << "source_fd->Read failed: " << bytes_read; + return false; + } + TEST_AND_RETURN_FALSE(cow_writer->AddRawBlocks( + cow_op.dst_block, buffer.data(), block_size)); + break; + } + } + + return true; +} +} // namespace + +size_t EstimateCowSize( + FileDescriptorPtr target_fd, + const google::protobuf::RepeatedPtrField<InstallOperation>& operations, + const google::protobuf::RepeatedPtrField<CowMergeOperation>& + merge_operations, + size_t block_size, + std::string compression) { + android::snapshot::CowWriter cow_writer{ + {.block_size = static_cast<uint32_t>(block_size), + .compression = std::move(compression)}}; + // CowWriter treats -1 as special value, will discard all the data but still + // reports Cow size. Good for estimation purposes + cow_writer.Initialize(android::base::borrowed_fd{-1}); + CHECK(CowDryRun( + target_fd, operations, merge_operations, block_size, &cow_writer)); + CHECK(cow_writer.Finalize()); + return cow_writer.GetCowSize(); +} + +bool CowDryRun( + FileDescriptorPtr target_fd, + const google::protobuf::RepeatedPtrField<InstallOperation>& operations, + const google::protobuf::RepeatedPtrField<CowMergeOperation>& + merge_operations, + size_t block_size, + android::snapshot::CowWriter* cow_writer) { + const auto converted = ConvertToCowOperations(operations, merge_operations); + WriteAllCowOps(block_size, converted, cow_writer, target_fd); + cow_writer->AddLabel(0); + for (const auto& op : operations) { + switch (op.type()) { + case InstallOperation::REPLACE: + case InstallOperation::REPLACE_BZ: + case InstallOperation::REPLACE_XZ: + TEST_AND_RETURN_FALSE( + PerformReplaceOp(op, cow_writer, target_fd, block_size)); + break; + case InstallOperation::ZERO: + case InstallOperation::DISCARD: + TEST_AND_RETURN_FALSE(PerformZeroOp(op, cow_writer, block_size)); + break; + case InstallOperation::SOURCE_COPY: + case InstallOperation::MOVE: + // Already handeled by WriteAllCowOps, + break; + case InstallOperation::SOURCE_BSDIFF: + case InstallOperation::BROTLI_BSDIFF: + case InstallOperation::PUFFDIFF: + case InstallOperation::BSDIFF: + // We might do something special by adding CowBsdiff to CowWriter. + // For now proceed the same way as normal REPLACE operation. + TEST_AND_RETURN_FALSE( + PerformReplaceOp(op, cow_writer, target_fd, block_size)); + break; + } + // Arbitrary label number, we won't be resuming use these labels here. + // They are emitted just to keep size estimates accurate. As update_engine + // emits 1 label for every op. + cow_writer->AddLabel(2); + } + // TODO(zhangkelvin) Take FEC extents into account once VABC stabilizes + return true; +} +} // namespace chromeos_update_engine diff --git a/payload_generator/cow_size_estimator.h b/payload_generator/cow_size_estimator.h new file mode 100644 index 00000000..850c8909 --- /dev/null +++ b/payload_generator/cow_size_estimator.h @@ -0,0 +1,47 @@ +// +// 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 <cstddef> +#include <string> + +#include <libsnapshot/cow_writer.h> +#include <update_engine/update_metadata.pb.h> + +#include "update_engine/payload_consumer/file_descriptor.h" + +namespace chromeos_update_engine { +// Given file descriptor to the target image, and list of +// operations, estimate the size of COW image if the operations are applied on +// Virtual AB Compression enabled device. This is intended to be used by update +// generators to put an estimate cow size in OTA payload. When installing an OTA +// update, libsnapshot will take this estimate as a hint to allocate spaces. +size_t EstimateCowSize( + FileDescriptorPtr target_fd, + const google::protobuf::RepeatedPtrField<InstallOperation>& operations, + const google::protobuf::RepeatedPtrField<CowMergeOperation>& + merge_operations, + size_t block_size, + std::string compression); + +// Convert InstallOps to CowOps and apply the converted cow op to |cow_writer| +bool CowDryRun( + FileDescriptorPtr target_fd, + const google::protobuf::RepeatedPtrField<InstallOperation>& operations, + const google::protobuf::RepeatedPtrField<CowMergeOperation>& + merge_operations, + size_t block_size, + android::snapshot::CowWriter* cow_writer); + +} // namespace chromeos_update_engine diff --git a/payload_generator/cow_size_estimator_stub.cc b/payload_generator/cow_size_estimator_stub.cc new file mode 100644 index 00000000..9d94d63b --- /dev/null +++ b/payload_generator/cow_size_estimator_stub.cc @@ -0,0 +1,31 @@ +// +// 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/cow_size_estimator.h" + +namespace chromeos_update_engine { + +size_t EstimateCowSize( + FileDescriptorPtr source_fd, + FileDescriptorPtr target_fd, + const google::protobuf::RepeatedPtrField<InstallOperation>& operations, + const google::protobuf::RepeatedPtrField<CowMergeOperation>& + merge_operations, + size_t block_size) { + return 0; +} + +} // namespace chromeos_update_engine diff --git a/payload_generator/cycle_breaker.cc b/payload_generator/cycle_breaker.cc deleted file mode 100644 index d6eeed2b..00000000 --- a/payload_generator/cycle_breaker.cc +++ /dev/null @@ -1,218 +0,0 @@ -// -// Copyright (C) 2012 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/cycle_breaker.h" - -#include <inttypes.h> - -#include <limits> -#include <set> -#include <string> -#include <utility> - -#include <base/stl_util.h> -#include <base/strings/string_util.h> -#include <base/strings/stringprintf.h> - -#include "update_engine/payload_generator/graph_utils.h" -#include "update_engine/payload_generator/tarjan.h" - -using std::make_pair; -using std::set; -using std::vector; - -namespace chromeos_update_engine { - -// This is the outer function from the original paper. -void CycleBreaker::BreakCycles(const Graph& graph, set<Edge>* out_cut_edges) { - cut_edges_.clear(); - - // Make a copy, which we will modify by removing edges. Thus, in each - // iteration subgraph_ is the current subgraph or the original with - // vertices we desire. This variable was "A_K" in the original paper. - subgraph_ = graph; - - // The paper calls for the "adjacency structure (i.e., graph) of - // strong (-ly connected) component K with least vertex in subgraph - // induced by {s, s + 1, ..., n}". - // We arbitrarily order each vertex by its index in the graph. Thus, - // each iteration, we are looking at the subgraph {s, s + 1, ..., n} - // and looking for the strongly connected component with vertex s. - - TarjanAlgorithm tarjan; - skipped_ops_ = 0; - - for (Graph::size_type i = 0; i < subgraph_.size(); i++) { - InstallOperation::Type op_type = graph[i].aop.op.type(); - if (op_type == InstallOperation::REPLACE || - op_type == InstallOperation::REPLACE_BZ) { - skipped_ops_++; - continue; - } - - if (i > 0) { - // Erase node (i - 1) from subgraph_. First, erase what it points to - subgraph_[i - 1].out_edges.clear(); - // Now, erase any pointers to node (i - 1) - for (Graph::size_type j = i; j < subgraph_.size(); j++) { - subgraph_[j].out_edges.erase(i - 1); - } - } - - // Calculate SCC (strongly connected component) with vertex i. - vector<Vertex::Index> component_indexes; - tarjan.Execute(i, &subgraph_, &component_indexes); - - // Set subgraph edges for the components in the SCC. - for (vector<Vertex::Index>::iterator it = component_indexes.begin(); - it != component_indexes.end(); - ++it) { - subgraph_[*it].subgraph_edges.clear(); - for (vector<Vertex::Index>::iterator jt = component_indexes.begin(); - jt != component_indexes.end(); - ++jt) { - // If there's a link from *it -> *jt in the graph, - // add a subgraph_ edge - if (base::ContainsKey(subgraph_[*it].out_edges, *jt)) - subgraph_[*it].subgraph_edges.insert(*jt); - } - } - - current_vertex_ = i; - blocked_.clear(); - blocked_.resize(subgraph_.size()); - blocked_graph_.clear(); - blocked_graph_.resize(subgraph_.size()); - Circuit(current_vertex_, 0); - } - - out_cut_edges->swap(cut_edges_); - LOG(INFO) << "Cycle breaker skipped " << skipped_ops_ << " ops."; - DCHECK(stack_.empty()); -} - -static const size_t kMaxEdgesToConsider = 2; - -void CycleBreaker::HandleCircuit() { - stack_.push_back(current_vertex_); - CHECK_GE(stack_.size(), static_cast<vector<Vertex::Index>::size_type>(2)); - Edge min_edge = make_pair(stack_[0], stack_[1]); - uint64_t min_edge_weight = std::numeric_limits<uint64_t>::max(); - size_t edges_considered = 0; - for (vector<Vertex::Index>::const_iterator it = stack_.begin(); - it != (stack_.end() - 1); - ++it) { - Edge edge = make_pair(*it, *(it + 1)); - if (cut_edges_.find(edge) != cut_edges_.end()) { - stack_.pop_back(); - return; - } - uint64_t edge_weight = graph_utils::EdgeWeight(subgraph_, edge); - if (edge_weight < min_edge_weight) { - min_edge_weight = edge_weight; - min_edge = edge; - } - edges_considered++; - if (edges_considered == kMaxEdgesToConsider) - break; - } - cut_edges_.insert(min_edge); - stack_.pop_back(); -} - -void CycleBreaker::Unblock(Vertex::Index u) { - blocked_[u] = false; - - for (Vertex::EdgeMap::iterator it = blocked_graph_[u].out_edges.begin(); - it != blocked_graph_[u].out_edges.end();) { - Vertex::Index w = it->first; - blocked_graph_[u].out_edges.erase(it++); - if (blocked_[w]) - Unblock(w); - } -} - -bool CycleBreaker::StackContainsCutEdge() const { - for (vector<Vertex::Index>::const_iterator it = ++stack_.begin(), - e = stack_.end(); - it != e; - ++it) { - Edge edge = make_pair(*(it - 1), *it); - if (base::ContainsKey(cut_edges_, edge)) { - return true; - } - } - return false; -} - -bool CycleBreaker::Circuit(Vertex::Index vertex, Vertex::Index depth) { - // "vertex" was "v" in the original paper. - bool found = false; // Was "f" in the original paper. - stack_.push_back(vertex); - blocked_[vertex] = true; - { - static int counter = 0; - counter++; - if (counter == 10000) { - counter = 0; - std::string stack_str; - for (Vertex::Index index : stack_) { - stack_str += std::to_string(index); - stack_str += " -> "; - } - LOG(INFO) << "stack: " << stack_str; - } - } - - for (Vertex::SubgraphEdgeMap::iterator w = - subgraph_[vertex].subgraph_edges.begin(); - w != subgraph_[vertex].subgraph_edges.end(); - ++w) { - if (*w == current_vertex_) { - // The original paper called for printing stack_ followed by - // current_vertex_ here, which is a cycle. Instead, we call - // HandleCircuit() to break it. - HandleCircuit(); - found = true; - } else if (!blocked_[*w]) { - if (Circuit(*w, depth + 1)) { - found = true; - if ((depth > kMaxEdgesToConsider) || StackContainsCutEdge()) - break; - } - } - } - - if (found) { - Unblock(vertex); - } else { - for (Vertex::SubgraphEdgeMap::iterator w = - subgraph_[vertex].subgraph_edges.begin(); - w != subgraph_[vertex].subgraph_edges.end(); - ++w) { - if (blocked_graph_[*w].out_edges.find(vertex) == - blocked_graph_[*w].out_edges.end()) { - blocked_graph_[*w].out_edges.insert( - make_pair(vertex, EdgeProperties())); - } - } - } - CHECK_EQ(vertex, stack_.back()); - stack_.pop_back(); - return found; -} - -} // namespace chromeos_update_engine diff --git a/payload_generator/cycle_breaker.h b/payload_generator/cycle_breaker.h deleted file mode 100644 index 01518fef..00000000 --- a/payload_generator/cycle_breaker.h +++ /dev/null @@ -1,71 +0,0 @@ -// -// Copyright (C) 2010 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_CYCLE_BREAKER_H_ -#define UPDATE_ENGINE_PAYLOAD_GENERATOR_CYCLE_BREAKER_H_ - -// This is a modified implementation of Donald B. Johnson's algorithm for -// finding all elementary cycles (a.k.a. circuits) in a directed graph. -// See the paper "Finding All the Elementary Circuits of a Directed Graph" -// at http://dutta.csc.ncsu.edu/csc791_spring07/wrap/circuits_johnson.pdf -// for reference. - -// Note: this version of the algorithm not only finds cycles, but breaks them. -// It uses a simple greedy algorithm for cutting: when a cycle is discovered, -// the edge with the least weight is cut. Longer term we may wish to do -// something more intelligent, since the goal is (ideally) to minimize the -// sum of the weights of all cut cycles. In practice, it's intractable -// to consider all cycles before cutting any; there are simply too many. -// In a sample graph representative of a typical workload, I found over -// 5 * 10^15 cycles. - -#include <set> -#include <vector> - -#include "update_engine/payload_generator/graph_types.h" - -namespace chromeos_update_engine { - -class CycleBreaker { - public: - CycleBreaker() : skipped_ops_(0) {} - // out_cut_edges is replaced with the cut edges. - void BreakCycles(const Graph& graph, std::set<Edge>* out_cut_edges); - - size_t skipped_ops() const { return skipped_ops_; } - - private: - void HandleCircuit(); - void Unblock(Vertex::Index u); - bool Circuit(Vertex::Index vertex, Vertex::Index depth); - bool StackContainsCutEdge() const; - - std::vector<bool> blocked_; // "blocked" in the paper - Vertex::Index current_vertex_; // "s" in the paper - std::vector<Vertex::Index> stack_; // the stack variable in the paper - Graph subgraph_; // "A_K" in the paper - Graph blocked_graph_; // "B" in the paper - - std::set<Edge> cut_edges_; - - // Number of operations skipped b/c we know they don't have any - // incoming edges. - size_t skipped_ops_; -}; - -} // namespace chromeos_update_engine - -#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_CYCLE_BREAKER_H_ diff --git a/payload_generator/cycle_breaker_unittest.cc b/payload_generator/cycle_breaker_unittest.cc deleted file mode 100644 index fdcf49be..00000000 --- a/payload_generator/cycle_breaker_unittest.cc +++ /dev/null @@ -1,279 +0,0 @@ -// -// Copyright (C) 2010 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/cycle_breaker.h" - -#include <set> -#include <string> -#include <utility> -#include <vector> - -#include <base/logging.h> -#include <base/stl_util.h> -#include <gtest/gtest.h> - -#include "update_engine/payload_generator/graph_types.h" - -using std::make_pair; -using std::pair; -using std::set; -using std::string; -using std::vector; - -namespace chromeos_update_engine { - -namespace { -void SetOpForNodes(Graph* graph) { - for (Vertex& vertex : *graph) { - vertex.aop.op.set_type(InstallOperation::MOVE); - } -} -} // namespace - -class CycleBreakerTest : public ::testing::Test {}; - -TEST(CycleBreakerTest, SimpleTest) { - int counter = 0; - const Vertex::Index n_a = counter++; - const Vertex::Index n_b = counter++; - const Vertex::Index n_c = counter++; - const Vertex::Index n_d = counter++; - const Vertex::Index n_e = counter++; - const Vertex::Index n_f = counter++; - const Vertex::Index n_g = counter++; - const Vertex::Index n_h = counter++; - const Graph::size_type kNodeCount = counter++; - - Graph graph(kNodeCount); - SetOpForNodes(&graph); - - graph[n_a].out_edges.insert(make_pair(n_e, EdgeProperties())); - graph[n_a].out_edges.insert(make_pair(n_f, EdgeProperties())); - graph[n_b].out_edges.insert(make_pair(n_a, EdgeProperties())); - graph[n_c].out_edges.insert(make_pair(n_d, EdgeProperties())); - graph[n_d].out_edges.insert(make_pair(n_e, EdgeProperties())); - graph[n_d].out_edges.insert(make_pair(n_f, EdgeProperties())); - graph[n_e].out_edges.insert(make_pair(n_b, EdgeProperties())); - graph[n_e].out_edges.insert(make_pair(n_c, EdgeProperties())); - graph[n_e].out_edges.insert(make_pair(n_f, EdgeProperties())); - graph[n_f].out_edges.insert(make_pair(n_g, EdgeProperties())); - graph[n_g].out_edges.insert(make_pair(n_h, EdgeProperties())); - graph[n_h].out_edges.insert(make_pair(n_g, EdgeProperties())); - - CycleBreaker breaker; - - set<Edge> broken_edges; - breaker.BreakCycles(graph, &broken_edges); - - // The following cycles must be cut: - // A->E->B - // C->D->E - // G->H - - EXPECT_TRUE(base::ContainsKey(broken_edges, make_pair(n_a, n_e)) || - base::ContainsKey(broken_edges, make_pair(n_e, n_b)) || - base::ContainsKey(broken_edges, make_pair(n_b, n_a))); - EXPECT_TRUE(base::ContainsKey(broken_edges, make_pair(n_c, n_d)) || - base::ContainsKey(broken_edges, make_pair(n_d, n_e)) || - base::ContainsKey(broken_edges, make_pair(n_e, n_c))); - EXPECT_TRUE(base::ContainsKey(broken_edges, make_pair(n_g, n_h)) || - base::ContainsKey(broken_edges, make_pair(n_h, n_g))); - EXPECT_EQ(3U, broken_edges.size()); -} - -namespace { -pair<Vertex::Index, EdgeProperties> EdgeWithWeight(Vertex::Index dest, - uint64_t weight) { - EdgeProperties props; - props.extents.resize(1); - props.extents[0].set_num_blocks(weight); - return make_pair(dest, props); -} -} // namespace - -// This creates a bunch of cycles like this: -// -// root <------. -// (t)-> / | \ | -// V V V | -// N N N | -// \ | / | -// VVV | -// N | -// / | \ | -// V V V | -// N N N | -// ... | -// (s)-> \ | / | -// VVV | -// N | -// \_________/ -// -// such that the original cutting algo would cut edges (s). We changed -// the algorithm to cut cycles (t) instead, since they are closer to the -// root, and that can massively speed up cycle cutting. -TEST(CycleBreakerTest, AggressiveCutTest) { - size_t counter = 0; - - const int kNodesPerGroup = 4; - const int kGroups = 33; - - Graph graph(kGroups * kNodesPerGroup + 1); // + 1 for the root node - SetOpForNodes(&graph); - - const Vertex::Index n_root = counter++; - - Vertex::Index last_hub = n_root; - for (int i = 0; i < kGroups; i++) { - uint64_t weight = 5; - if (i == 0) - weight = 2; - else if (i == (kGroups - 1)) - weight = 1; - - const Vertex::Index next_hub = counter++; - - for (int j = 0; j < (kNodesPerGroup - 1); j++) { - const Vertex::Index node = counter++; - graph[last_hub].out_edges.insert(EdgeWithWeight(node, weight)); - graph[node].out_edges.insert(EdgeWithWeight(next_hub, weight)); - } - last_hub = next_hub; - } - - graph[last_hub].out_edges.insert(EdgeWithWeight(n_root, 5)); - - EXPECT_EQ(counter, graph.size()); - - CycleBreaker breaker; - - set<Edge> broken_edges; - LOG(INFO) << "If this hangs for more than 1 second, the test has failed."; - breaker.BreakCycles(graph, &broken_edges); - - set<Edge> expected_cuts; - - for (Vertex::EdgeMap::const_iterator it = graph[n_root].out_edges.begin(), - e = graph[n_root].out_edges.end(); - it != e; - ++it) { - expected_cuts.insert(make_pair(n_root, it->first)); - } - - EXPECT_TRUE(broken_edges == expected_cuts); -} - -TEST(CycleBreakerTest, WeightTest) { - size_t counter = 0; - const Vertex::Index n_a = counter++; - const Vertex::Index n_b = counter++; - const Vertex::Index n_c = counter++; - const Vertex::Index n_d = counter++; - const Vertex::Index n_e = counter++; - const Vertex::Index n_f = counter++; - const Vertex::Index n_g = counter++; - const Vertex::Index n_h = counter++; - const Vertex::Index n_i = counter++; - const Vertex::Index n_j = counter++; - const Graph::size_type kNodeCount = counter++; - - Graph graph(kNodeCount); - SetOpForNodes(&graph); - - graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 4)); - graph[n_a].out_edges.insert(EdgeWithWeight(n_f, 3)); - graph[n_a].out_edges.insert(EdgeWithWeight(n_h, 2)); - graph[n_b].out_edges.insert(EdgeWithWeight(n_a, 3)); - graph[n_b].out_edges.insert(EdgeWithWeight(n_c, 4)); - graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 5)); - graph[n_c].out_edges.insert(EdgeWithWeight(n_d, 3)); - graph[n_d].out_edges.insert(EdgeWithWeight(n_a, 6)); - graph[n_d].out_edges.insert(EdgeWithWeight(n_e, 3)); - graph[n_e].out_edges.insert(EdgeWithWeight(n_d, 4)); - graph[n_e].out_edges.insert(EdgeWithWeight(n_g, 5)); - graph[n_f].out_edges.insert(EdgeWithWeight(n_g, 2)); - graph[n_g].out_edges.insert(EdgeWithWeight(n_f, 3)); - graph[n_g].out_edges.insert(EdgeWithWeight(n_d, 5)); - graph[n_h].out_edges.insert(EdgeWithWeight(n_i, 8)); - graph[n_i].out_edges.insert(EdgeWithWeight(n_e, 4)); - graph[n_i].out_edges.insert(EdgeWithWeight(n_h, 9)); - graph[n_i].out_edges.insert(EdgeWithWeight(n_j, 6)); - - CycleBreaker breaker; - - set<Edge> broken_edges; - breaker.BreakCycles(graph, &broken_edges); - - // These are required to be broken: - EXPECT_TRUE(base::ContainsKey(broken_edges, make_pair(n_b, n_a))); - EXPECT_TRUE(base::ContainsKey(broken_edges, make_pair(n_b, n_c))); - EXPECT_TRUE(base::ContainsKey(broken_edges, make_pair(n_d, n_e))); - EXPECT_TRUE(base::ContainsKey(broken_edges, make_pair(n_f, n_g))); - EXPECT_TRUE(base::ContainsKey(broken_edges, make_pair(n_h, n_i))); -} - -TEST(CycleBreakerTest, UnblockGraphTest) { - size_t counter = 0; - const Vertex::Index n_a = counter++; - const Vertex::Index n_b = counter++; - const Vertex::Index n_c = counter++; - const Vertex::Index n_d = counter++; - const Graph::size_type kNodeCount = counter++; - - Graph graph(kNodeCount); - SetOpForNodes(&graph); - - graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 1)); - graph[n_a].out_edges.insert(EdgeWithWeight(n_c, 1)); - graph[n_b].out_edges.insert(EdgeWithWeight(n_c, 2)); - graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 2)); - graph[n_b].out_edges.insert(EdgeWithWeight(n_d, 2)); - graph[n_d].out_edges.insert(EdgeWithWeight(n_a, 2)); - - CycleBreaker breaker; - - set<Edge> broken_edges; - breaker.BreakCycles(graph, &broken_edges); - - // These are required to be broken: - EXPECT_TRUE(base::ContainsKey(broken_edges, make_pair(n_a, n_b))); - EXPECT_TRUE(base::ContainsKey(broken_edges, make_pair(n_a, n_c))); -} - -TEST(CycleBreakerTest, SkipOpsTest) { - size_t counter = 0; - const Vertex::Index n_a = counter++; - const Vertex::Index n_b = counter++; - const Vertex::Index n_c = counter++; - const Graph::size_type kNodeCount = counter++; - - Graph graph(kNodeCount); - SetOpForNodes(&graph); - graph[n_a].aop.op.set_type(InstallOperation::REPLACE_BZ); - graph[n_c].aop.op.set_type(InstallOperation::REPLACE); - - graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 1)); - graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 1)); - - CycleBreaker breaker; - - set<Edge> broken_edges; - breaker.BreakCycles(graph, &broken_edges); - - EXPECT_EQ(2U, breaker.skipped_ops()); -} - -} // namespace chromeos_update_engine diff --git a/payload_generator/deflate_utils.cc b/payload_generator/deflate_utils.cc index 01402dd6..c874bfd2 100644 --- a/payload_generator/deflate_utils.cc +++ b/payload_generator/deflate_utils.cc @@ -46,7 +46,7 @@ const uint64_t kMinimumSquashfsImageSize = 1 * 1024 * 1024; // bytes // TODO(*): Optimize this so we don't have to read all extents into memory in // case it is large. bool CopyExtentsToFile(const string& in_path, - const vector<Extent> extents, + const vector<Extent>& extents, const string& out_path, size_t block_size) { brillo::Blob data(utils::BlocksInExtents(extents) * block_size); @@ -284,8 +284,9 @@ bool PreprocessPartitionFiles(const PartitionConfig& part, TEST_AND_RETURN_FALSE( CopyExtentsToFile(part.path, file.extents, path.value(), kBlockSize)); // Test if it is actually a Squashfs file. - auto sqfs = - SquashfsFilesystem::CreateFromFile(path.value(), extract_deflates); + auto sqfs = SquashfsFilesystem::CreateFromFile(path.value(), + extract_deflates, + /*load_settings=*/false); if (sqfs) { // It is an squashfs file. Get its files to replace with itself. vector<FilesystemInterface::File> files; @@ -306,7 +307,7 @@ bool PreprocessPartitionFiles(const PartitionConfig& part, } } - if (is_regular_file && extract_deflates) { + 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 d484d32b..ab0f0364 100644 --- a/payload_generator/delta_diff_generator.cc +++ b/payload_generator/delta_diff_generator.cc @@ -29,16 +29,21 @@ #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/file_descriptor.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/cow_size_estimator.h" #include "update_engine/payload_generator/delta_diff_utils.h" #include "update_engine/payload_generator/full_update_generator.h" -#include "update_engine/payload_generator/inplace_generator.h" +#include "update_engine/payload_generator/merge_sequence_generator.h" #include "update_engine/payload_generator/payload_file.h" +#include "update_engine/update_metadata.pb.h" using std::string; using std::unique_ptr; @@ -50,6 +55,110 @@ 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 { + bool IsDynamicPartition(const std::string& partition_name) { + for (const auto& group : + config_.target.dynamic_partition_metadata->groups()) { + const auto& names = group.partition_names(); + if (std::find(names.begin(), names.end(), partition_name) != + names.end()) { + return true; + } + } + return false; + } + + 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, + size_t* cow_size, + 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), + cow_size_(cow_size), + strategy_(std::move(strategy)) {} + PartitionProcessor(PartitionProcessor&&) noexcept = default; + + void Run() override { + LOG(INFO) << "Started an async task to process partition " + << new_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 (!snapshot_enabled || !IsDynamicPartition(new_part_.name)) { + return; + } + // Skip cow size estimation if VABC isn't enabled + if (!config_.target.dynamic_partition_metadata->vabc_enabled()) { + return; + } + if (!old_part_.path.empty()) { + auto generator = MergeSequenceGenerator::Create(*aops_); + if (!generator || !generator->Generate(cow_merge_sequence_)) { + LOG(FATAL) << "Failed to generate merge sequence"; + } + } + + LOG(INFO) << "Estimating COW size for partition: " << new_part_.name; + // Need the contents of source/target image bytes when doing + // dry run. + FileDescriptorPtr source_fd{new EintrSafeFileDescriptor()}; + source_fd->Open(old_part_.path.c_str(), O_RDONLY); + + auto target_fd = std::make_unique<EintrSafeFileDescriptor>(); + target_fd->Open(new_part_.path.c_str(), O_RDONLY); + + google::protobuf::RepeatedPtrField<InstallOperation> operations; + + for (const AnnotatedOperation& aop : *aops_) { + *operations.Add() = aop.op; + } + *cow_size_ = EstimateCowSize( + std::move(target_fd), + std::move(operations), + {cow_merge_sequence_->begin(), cow_merge_sequence_->end()}, + config_.block_size, + config_.target.dynamic_partition_metadata->vabc_compression_param()); + if (!new_part_.disable_fec_computation) { + *cow_size_ += + new_part_.verity.fec_extent.num_blocks() * config_.block_size; + } + *cow_size_ += + new_part_.verity.hash_tree_extent.num_blocks() * config_.block_size; + LOG(INFO) << "Estimated COW size for partition: " << new_part_.name << " " + << *cow_size_; + } + + 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_; + size_t* cow_size_; + 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, @@ -64,23 +173,28 @@ bool GenerateUpdatePayloadFile(const PayloadGenerationConfig& config, PayloadFile payload; TEST_AND_RETURN_FALSE(payload.Init(config)); - const string kTempFileTemplate("CrAU_temp_data.XXXXXX"); - string temp_file_path; - int data_file_fd; - TEST_AND_RETURN_FALSE( - utils::MakeTempFile(kTempFileTemplate, &temp_file_path, &data_file_fd)); - ScopedPathUnlinker temp_file_unlinker(temp_file_path); - TEST_AND_RETURN_FALSE(data_file_fd >= 0); - + ScopedTempFile data_file("CrAU_temp_data.XXXXXX", true); { off_t data_file_size = 0; - ScopedFdCloser data_file_fd_closer(&data_file_fd); - BlobFileWriter blob_file(data_file_fd, &data_file_size); + BlobFileWriter blob_file(data_file.fd(), &data_file_size); if (config.is_delta) { TEST_AND_RETURN_FALSE(config.source.partitions.size() == 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<size_t> all_cow_sizes(config.target.partitions.size(), 0); + + 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; @@ -93,36 +207,49 @@ bool GenerateUpdatePayloadFile(const PayloadGenerationConfig& config, unique_ptr<OperationsGenerator> strategy; if (!old_part.path.empty()) { // Delta update. - if (config.version.minor == kInPlaceMinorPayloadVersion) { - LOG(INFO) << "Using generator InplaceGenerator()."; - strategy.reset(new InplaceGenerator()); - } else { - LOG(INFO) << "Using generator ABGenerator()."; - strategy.reset(new ABGenerator()); - } + LOG(INFO) << "Using generator ABGenerator() for partition " + << new_part.name; + strategy.reset(new ABGenerator()); } else { - LOG(INFO) << "Using generator FullUpdateGenerator()."; + LOG(INFO) << "Using generator FullUpdateGenerator() for partition " + << new_part.name; 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)); - - // Filter the no-operations. OperationsGenerators should not output this - // kind of operations normally, but this is an extra step to fix that if - // happened. - diff_utils::FilterNoopOperations(&aops); + partition_tasks.push_back(PartitionProcessor(config, + old_part, + new_part, + &blob_file, + &all_aops[i], + &all_merge_sequences[i], + &all_cow_sizes[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]), + all_cow_sizes[i])); } } + data_file.CloseFd(); LOG(INFO) << "Writing payload file..."; // Write payload file to disk. TEST_AND_RETURN_FALSE(payload.WritePayload( - output_path, temp_file_path, private_key_path, metadata_size)); + output_path, data_file.path(), private_key_path, metadata_size)); LOG(INFO) << "All done. Successfully created delta file with " << "metadata size = " << *metadata_size; diff --git a/payload_generator/delta_diff_utils.cc b/payload_generator/delta_diff_utils.cc index 4ba6e24f..3c025e10 100644 --- a/payload_generator/delta_diff_utils.cc +++ b/payload_generator/delta_diff_utils.cc @@ -83,103 +83,6 @@ const uint64_t kMaxPuffdiffDestinationSize = 150 * 1024 * 1024; // bytes const int kBrotliCompressionQuality = 11; -// Process a range of blocks from |range_start| to |range_end| in the extent at -// position |*idx_p| of |extents|. If |do_remove| is true, this range will be -// removed, which may cause the extent to be trimmed, split or removed entirely. -// The value of |*idx_p| is updated to point to the next extent to be processed. -// Returns true iff the next extent to process is a new or updated one. -bool ProcessExtentBlockRange(vector<Extent>* extents, - size_t* idx_p, - const bool do_remove, - uint64_t range_start, - uint64_t range_end) { - size_t idx = *idx_p; - uint64_t start_block = (*extents)[idx].start_block(); - uint64_t num_blocks = (*extents)[idx].num_blocks(); - uint64_t range_size = range_end - range_start; - - if (do_remove) { - if (range_size == num_blocks) { - // Remove the entire extent. - extents->erase(extents->begin() + idx); - } else if (range_end == num_blocks) { - // Trim the end of the extent. - (*extents)[idx].set_num_blocks(num_blocks - range_size); - idx++; - } else if (range_start == 0) { - // Trim the head of the extent. - (*extents)[idx].set_start_block(start_block + range_size); - (*extents)[idx].set_num_blocks(num_blocks - range_size); - } else { - // Trim the middle, splitting the remainder into two parts. - (*extents)[idx].set_num_blocks(range_start); - Extent e; - e.set_start_block(start_block + range_end); - e.set_num_blocks(num_blocks - range_end); - idx++; - extents->insert(extents->begin() + idx, e); - } - } else if (range_end == num_blocks) { - // Done with this extent. - idx++; - } else { - return false; - } - - *idx_p = idx; - return true; -} - -// Remove identical corresponding block ranges in |src_extents| and -// |dst_extents|. Used for preventing moving of blocks onto themselves during -// MOVE operations. The value of |total_bytes| indicates the actual length of -// content; this may be slightly less than the total size of blocks, in which -// case the last block is only partly occupied with data. Returns the total -// number of bytes removed. -size_t RemoveIdenticalBlockRanges(vector<Extent>* src_extents, - vector<Extent>* dst_extents, - const size_t total_bytes) { - size_t src_idx = 0; - size_t dst_idx = 0; - uint64_t src_offset = 0, dst_offset = 0; - size_t removed_bytes = 0, nonfull_block_bytes; - bool do_remove = false; - while (src_idx < src_extents->size() && dst_idx < dst_extents->size()) { - do_remove = ((*src_extents)[src_idx].start_block() + src_offset == - (*dst_extents)[dst_idx].start_block() + dst_offset); - - uint64_t src_num_blocks = (*src_extents)[src_idx].num_blocks(); - uint64_t dst_num_blocks = (*dst_extents)[dst_idx].num_blocks(); - uint64_t min_num_blocks = - std::min(src_num_blocks - src_offset, dst_num_blocks - dst_offset); - uint64_t prev_src_offset = src_offset; - uint64_t prev_dst_offset = dst_offset; - src_offset += min_num_blocks; - dst_offset += min_num_blocks; - - bool new_src = ProcessExtentBlockRange( - src_extents, &src_idx, do_remove, prev_src_offset, src_offset); - bool new_dst = ProcessExtentBlockRange( - dst_extents, &dst_idx, do_remove, prev_dst_offset, dst_offset); - if (new_src) { - src_offset = 0; - } - if (new_dst) { - dst_offset = 0; - } - - if (do_remove) - removed_bytes += min_num_blocks * kBlockSize; - } - - // If we removed the last block and this block is only partly used by file - // content, deduct the unused portion from the total removed byte count. - if (do_remove && (nonfull_block_bytes = total_bytes % kBlockSize)) - removed_bytes -= kBlockSize - nonfull_block_bytes; - - return removed_bytes; -} - // Storing a diff operation has more overhead over replace operation in the // manifest, we need to store an additional src_sha256_hash which is 32 bytes // and not compressible, and also src_extents which could use anywhere from a @@ -318,13 +221,11 @@ void FileDeltaProcessor::Run() { return; } - if (!version_.InplaceUpdate()) { - if (!ABGenerator::FragmentOperations( - version_, &file_aops_, new_part_, blob_file_)) { - LOG(ERROR) << "Failed to fragment operations for " << name_; - failed_ = true; - return; - } + if (!ABGenerator::FragmentOperations( + version_, &file_aops_, new_part_, blob_file_)) { + LOG(ERROR) << "Failed to fragment operations for " << name_; + failed_ = true; + return; } LOG(INFO) << "Encoded file " << name_ << " (" << new_extents_blocks_ @@ -349,12 +250,13 @@ FilesystemInterface::File GetOldFile( if (old_file_iter != old_files_map.end()) return old_file_iter->second; - // No old file match for the new file name, use a similar file with the - // shortest levenshtein distance. + // No old file matches the new file name. Use a similar file with the + // shortest levenshtein distance instead. // This works great if the file has version number in it, but even for // a completely new file, using a similar file can still help. - int min_distance = new_file_name.size(); - const FilesystemInterface::File* old_file; + int min_distance = + LevenshteinDistance(new_file_name, old_files_map.begin()->first); + const FilesystemInterface::File* old_file = &old_files_map.begin()->second; for (const auto& pair : old_files_map) { int distance = LevenshteinDistance(new_file_name, pair.first); if (distance < min_distance) { @@ -447,12 +349,8 @@ bool DeltaReadPartition(vector<AnnotatedOperation>* aops, // from the same source blocks. At that time, this code can die. -adlr FilesystemInterface::File old_file = GetOldFile(old_files_map, new_file.name); - vector<Extent> old_file_extents; - if (version.InplaceUpdate()) - old_file_extents = - FilterExtentRanges(old_file.extents, old_visited_blocks); - else - old_file_extents = FilterExtentRanges(old_file.extents, old_zero_blocks); + auto old_file_extents = + FilterExtentRanges(old_file.extents, old_zero_blocks); old_visited_blocks.AddExtents(old_file_extents); file_delta_processors.emplace_back(old_part.path, @@ -541,21 +439,6 @@ bool DeltaMovedAndZeroBlocks(vector<AnnotatedOperation>* aops, &old_block_ids, &new_block_ids)); - // If the update is inplace, we map all the blocks that didn't move, - // regardless of the contents since they are already copied and no operation - // is required. - if (version.InplaceUpdate()) { - uint64_t num_blocks = std::min(old_num_blocks, new_num_blocks); - for (uint64_t block = 0; block < num_blocks; block++) { - if (old_block_ids[block] == new_block_ids[block] && - !old_visited_blocks->ContainsBlock(block) && - !new_visited_blocks->ContainsBlock(block)) { - old_visited_blocks->AddBlock(block); - new_visited_blocks->AddBlock(block); - } - } - } - // A mapping from the block_id to the list of block numbers with that block id // in the old partition. This is used to lookup where in the old partition // is a block from the new partition. @@ -602,10 +485,6 @@ bool DeltaMovedAndZeroBlocks(vector<AnnotatedOperation>* aops, AppendBlockToExtents(&old_identical_blocks, old_blocks_map_it->second.back()); AppendBlockToExtents(&new_identical_blocks, block); - // We can't reuse source blocks in minor version 1 because the cycle - // breaking algorithm used in the in-place update doesn't support that. - if (version.InplaceUpdate()) - old_blocks_map_it->second.pop_back(); } if (chunk_blocks == -1) @@ -657,9 +536,7 @@ bool DeltaMovedAndZeroBlocks(vector<AnnotatedOperation>* aops, aops->emplace_back(); AnnotatedOperation* aop = &aops->back(); aop->name = "<identical-blocks>"; - aop->op.set_type(version.OperationAllowed(InstallOperation::SOURCE_COPY) - ? InstallOperation::SOURCE_COPY - : InstallOperation::MOVE); + aop->op.set_type(InstallOperation::SOURCE_COPY); uint64_t chunk_num_blocks = std::min(static_cast<uint64_t>(extent.num_blocks()) - op_block_offset, @@ -704,6 +581,11 @@ bool DeltaReadFile(vector<AnnotatedOperation>* aops, InstallOperation operation; uint64_t total_blocks = utils::BlocksInExtents(new_extents); + if (chunk_blocks == 0) { + LOG(ERROR) << "Invalid number of chunk_blocks. Cannot be 0."; + return false; + } + if (chunk_blocks == -1) chunk_blocks = total_blocks; @@ -732,13 +614,8 @@ bool DeltaReadFile(vector<AnnotatedOperation>* aops, // Check if the operation writes nothing. if (operation.dst_extents_size() == 0) { - if (operation.type() == InstallOperation::MOVE) { - LOG(INFO) << "Empty MOVE operation (" << name << "), skipping"; - continue; - } else { - LOG(ERROR) << "Empty non-MOVE operation"; - return false; - } + LOG(ERROR) << "Empty non-MOVE operation"; + return false; } // Now, insert into the list of operations. @@ -828,19 +705,18 @@ bool ReadExtentsToDiff(const string& old_part, // Disable bsdiff, and puffdiff when the data is too big. bool bsdiff_allowed = - version.OperationAllowed(InstallOperation::SOURCE_BSDIFF) || - version.OperationAllowed(InstallOperation::BSDIFF); + version.OperationAllowed(InstallOperation::SOURCE_BSDIFF); if (bsdiff_allowed && blocks_to_read * kBlockSize > kMaxBsdiffDestinationSize) { - LOG(INFO) << "bsdiff blacklisted, data too big: " - << blocks_to_read * kBlockSize << " bytes"; + LOG(INFO) << "bsdiff ignored, data too big: " << blocks_to_read * kBlockSize + << " bytes"; bsdiff_allowed = false; } bool puffdiff_allowed = version.OperationAllowed(InstallOperation::PUFFDIFF); if (puffdiff_allowed && blocks_to_read * kBlockSize > kMaxPuffdiffDestinationSize) { - LOG(INFO) << "puffdiff blacklisted, data too big: " + LOG(INFO) << "puffdiff ignored, data too big: " << blocks_to_read * kBlockSize << " bytes"; puffdiff_allowed = false; } @@ -878,9 +754,7 @@ bool ReadExtentsToDiff(const string& old_part, kBlockSize)); if (old_data == new_data) { // No change in data. - operation.set_type(version.OperationAllowed(InstallOperation::SOURCE_COPY) - ? InstallOperation::SOURCE_COPY - : InstallOperation::MOVE); + operation.set_type(InstallOperation::SOURCE_COPY); data_blob = brillo::Blob(); } else if (IsDiffOperationBetter( operation, data_blob.size(), 0, src_extents.size())) { @@ -892,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::BSDIFF; + InstallOperation::Type operation_type = InstallOperation::SOURCE_BSDIFF; if (version.OperationAllowed(InstallOperation::BROTLI_BSDIFF)) { bsdiff_patch_writer = bsdiff::CreateBSDF2PatchWriter(patch.value(), @@ -901,9 +775,6 @@ bool ReadExtentsToDiff(const string& old_part, operation_type = InstallOperation::BROTLI_BSDIFF; } else { bsdiff_patch_writer = bsdiff::CreateBsdiffPatchWriter(patch.value()); - if (version.OperationAllowed(InstallOperation::SOURCE_BSDIFF)) { - operation_type = InstallOperation::SOURCE_BSDIFF; - } } brillo::Blob bsdiff_delta; @@ -951,17 +822,13 @@ bool ReadExtentsToDiff(const string& old_part, // Only Puffdiff if both files have at least one deflate left. if (!src_deflates.empty() && !dst_deflates.empty()) { brillo::Blob puffdiff_delta; - string temp_file_path; - TEST_AND_RETURN_FALSE(utils::MakeTempFile( - "puffdiff-delta.XXXXXX", &temp_file_path, nullptr)); - ScopedPathUnlinker temp_file_unlinker(temp_file_path); - + ScopedTempFile temp_file("puffdiff-delta.XXXXXX"); // Perform PuffDiff operation. TEST_AND_RETURN_FALSE(puffin::PuffDiff(old_data, new_data, src_deflates, dst_deflates, - temp_file_path, + temp_file.path(), &puffdiff_delta)); TEST_AND_RETURN_FALSE(puffdiff_delta.size() > 0); if (IsDiffOperationBetter(operation, @@ -976,23 +843,14 @@ bool ReadExtentsToDiff(const string& old_part, } } - // Remove identical src/dst block ranges in MOVE operations. - if (operation.type() == InstallOperation::MOVE) { - auto removed_bytes = - RemoveIdenticalBlockRanges(&src_extents, &dst_extents, new_data.size()); - operation.set_src_length(old_data.size() - removed_bytes); - operation.set_dst_length(new_data.size() - removed_bytes); - } - // WARNING: We always set legacy |src_length| and |dst_length| fields for // BSDIFF. For SOURCE_BSDIFF we only set them for minor version 3 and // lower. This is needed because we used to use these two parameters in the // SOURCE_BSDIFF for minor version 3 and lower, but we do not need them // anymore in higher minor versions. This means if we stop adding these // parameters for those minor versions, the delta payloads will be invalid. - if (operation.type() == InstallOperation::BSDIFF || - (operation.type() == InstallOperation::SOURCE_BSDIFF && - version.minor <= kOpSrcHashMinorPayloadVersion)) { + if (operation.type() == InstallOperation::SOURCE_BSDIFF && + version.minor <= kOpSrcHashMinorPayloadVersion) { operation.set_src_length(old_data.size()); operation.set_dst_length(new_data.size()); } @@ -1021,22 +879,6 @@ bool IsNoSourceOperation(InstallOperation::Type op_type) { op_type == InstallOperation::DISCARD); } -// Returns true if |op| is a no-op operation that doesn't do any useful work -// (e.g., a move operation that copies blocks onto themselves). -bool IsNoopOperation(const InstallOperation& op) { - return (op.type() == InstallOperation::MOVE && - ExpandExtents(op.src_extents()) == ExpandExtents(op.dst_extents())); -} - -void FilterNoopOperations(vector<AnnotatedOperation>* ops) { - ops->erase(std::remove_if(ops->begin(), - ops->end(), - [](const AnnotatedOperation& aop) { - return IsNoopOperation(aop.op); - }), - ops->end()); -} - bool InitializePartitionInfo(const PartitionConfig& part, PartitionInfo* info) { info->set_size(part.size); HashCalculator hasher; @@ -1092,7 +934,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 2211b307..c75d16d9 100644 --- a/payload_generator/delta_diff_utils.h +++ b/payload_generator/delta_diff_utils.h @@ -127,14 +127,6 @@ 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); -// Returns true if |op| is a no-op operation that doesn't do any useful work -// (e.g., a move operation that copies blocks onto themselves). -bool IsNoopOperation(const InstallOperation& op); - -// Filters all the operations that are no-op, maintaining the relative order -// of the rest of the operations. -void FilterNoopOperations(std::vector<AnnotatedOperation>* ops); - 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 b2950e8b..f2db1bdc 100644 --- a/payload_generator/delta_diff_utils_unittest.cc +++ b/payload_generator/delta_diff_utils_unittest.cc @@ -69,13 +69,12 @@ bool WriteExtents(const string& part_path, // Create a fake filesystem of the given |size| and initialize the partition // holding it in the PartitionConfig |part|. void CreatePartition(PartitionConfig* part, - const string& pattern, + ScopedTempFile* part_file, uint64_t block_size, off_t size) { - int fd = -1; - ASSERT_TRUE(utils::MakeTempFile(pattern.c_str(), &part->path, &fd)); - ASSERT_EQ(0, ftruncate(fd, size)); - ASSERT_EQ(0, close(fd)); + part->path = part_file->path(); + ASSERT_EQ(0, ftruncate(part_file->fd(), size)); + part_file->CloseFd(); part->fs_interface.reset(new FakeFilesystem(block_size, size / block_size)); part->size = size; } @@ -112,31 +111,21 @@ class DeltaDiffUtilsTest : public ::testing::Test { void SetUp() override { CreatePartition(&old_part_, - "DeltaDiffUtilsTest-old_part-XXXXXX", + &old_part_file_, block_size_, block_size_ * kDefaultBlockCount); CreatePartition(&new_part_, - "DeltaDiffUtilsTest-old_part-XXXXXX", + &new_part_file_, block_size_, block_size_ * kDefaultBlockCount); - ASSERT_TRUE(utils::MakeTempFile( - "DeltaDiffUtilsTest-blob-XXXXXX", &blob_path_, &blob_fd_)); - } - - void TearDown() override { - unlink(old_part_.path.c_str()); - unlink(new_part_.path.c_str()); - if (blob_fd_ != -1) - close(blob_fd_); - unlink(blob_path_.c_str()); } // Helper function to call DeltaMovedAndZeroBlocks() using this class' data // members. This simply avoids repeating all the arguments that never change. bool RunDeltaMovedAndZeroBlocks(ssize_t chunk_blocks, uint32_t minor_version) { - BlobFileWriter blob_file(blob_fd_, &blob_size_); - PayloadVersion version(kChromeOSMajorPayloadVersion, minor_version); + BlobFileWriter blob_file(tmp_blob_file_.fd(), &blob_size_); + PayloadVersion version(kBrilloMajorPayloadVersion, minor_version); ExtentRanges old_zero_blocks; return diff_utils::DeltaMovedAndZeroBlocks(&aops_, old_part_.path, @@ -155,10 +144,11 @@ class DeltaDiffUtilsTest : public ::testing::Test { // with PartitionConfig old_part_{"part"}; PartitionConfig new_part_{"part"}; + ScopedTempFile old_part_file_{"DeltaDiffUtilsTest-old_part-XXXXXX", true}; + ScopedTempFile new_part_file_{"DeltaDiffUtilsTest-new_part-XXXXXX", true}; // The file holding the output blob from the various diff utils functions. - string blob_path_; - int blob_fd_{-1}; + ScopedTempFile tmp_blob_file_{"DeltaDiffUtilsTest-blob-XXXXXX", true}; off_t blob_size_{0}; size_t block_size_{kBlockSize}; @@ -173,7 +163,7 @@ TEST_F(DeltaDiffUtilsTest, SkipVerityExtentsTest) { new_part_.verity.hash_tree_extent = ExtentForRange(20, 30); new_part_.verity.fec_extent = ExtentForRange(40, 50); - BlobFileWriter blob_file(blob_fd_, &blob_size_); + BlobFileWriter blob_file(tmp_blob_file_.fd(), &blob_size_); EXPECT_TRUE(diff_utils::DeltaReadPartition( &aops_, old_part_, @@ -194,164 +184,6 @@ TEST_F(DeltaDiffUtilsTest, SkipVerityExtentsTest) { } } -TEST_F(DeltaDiffUtilsTest, MoveSmallTest) { - brillo::Blob data_blob(block_size_); - test_utils::FillWithData(&data_blob); - - // The old file is on a different block than the new one. - vector<Extent> old_extents = {ExtentForRange(11, 1)}; - vector<Extent> new_extents = {ExtentForRange(1, 1)}; - - EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob)); - EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob)); - - brillo::Blob data; - InstallOperation op; - EXPECT_TRUE(diff_utils::ReadExtentsToDiff( - old_part_.path, - new_part_.path, - old_extents, - new_extents, - {}, // old_deflates - {}, // new_deflates - PayloadVersion(kChromeOSMajorPayloadVersion, kInPlaceMinorPayloadVersion), - &data, - &op)); - EXPECT_TRUE(data.empty()); - - EXPECT_TRUE(op.has_type()); - EXPECT_EQ(InstallOperation::MOVE, op.type()); - EXPECT_FALSE(op.has_data_offset()); - EXPECT_FALSE(op.has_data_length()); - EXPECT_EQ(1, op.src_extents_size()); - EXPECT_EQ(kBlockSize, op.src_length()); - EXPECT_EQ(1, op.dst_extents_size()); - EXPECT_EQ(kBlockSize, op.dst_length()); - EXPECT_EQ(utils::BlocksInExtents(op.src_extents()), - utils::BlocksInExtents(op.dst_extents())); - EXPECT_EQ(1U, utils::BlocksInExtents(op.dst_extents())); -} - -TEST_F(DeltaDiffUtilsTest, MoveWithSameBlock) { - // Setup the old/new files so that it has immobile chunks; we make sure to - // utilize all sub-cases of such chunks: blocks 21--22 induce a split (src) - // and complete removal (dst), whereas blocks 24--25 induce trimming of the - // tail (src) and head (dst) of extents. The final block (29) is used for - // ensuring we properly account for the number of bytes removed in cases where - // the last block is partly filled. The detailed configuration: - // - // Old: [ 20 21 22 23 24 25 ] [ 28 29 ] - // New: [ 18 ] [ 21 22 ] [ 20 ] [ 24 25 26 ] [ 29 ] - // Same: ^^ ^^ ^^ ^^ ^^ - vector<Extent> old_extents = {ExtentForRange(20, 6), ExtentForRange(28, 2)}; - vector<Extent> new_extents = {ExtentForRange(18, 1), - ExtentForRange(21, 2), - ExtentForRange(20, 1), - ExtentForRange(24, 3), - ExtentForRange(29, 1)}; - - uint64_t num_blocks = utils::BlocksInExtents(old_extents); - EXPECT_EQ(num_blocks, utils::BlocksInExtents(new_extents)); - - // The size of the data should match the total number of blocks. Each block - // has a different content. - brillo::Blob file_data; - for (uint64_t i = 0; i < num_blocks; ++i) { - file_data.resize(file_data.size() + kBlockSize, 'a' + i); - } - - EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, file_data)); - EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, file_data)); - - brillo::Blob data; - InstallOperation op; - EXPECT_TRUE(diff_utils::ReadExtentsToDiff( - old_part_.path, - new_part_.path, - old_extents, - new_extents, - {}, // old_deflates - {}, // new_deflates - PayloadVersion(kChromeOSMajorPayloadVersion, kInPlaceMinorPayloadVersion), - &data, - &op)); - - EXPECT_TRUE(data.empty()); - - EXPECT_TRUE(op.has_type()); - EXPECT_EQ(InstallOperation::MOVE, op.type()); - EXPECT_FALSE(op.has_data_offset()); - EXPECT_FALSE(op.has_data_length()); - - // The expected old and new extents that actually moved. See comment above. - old_extents = { - ExtentForRange(20, 1), ExtentForRange(23, 1), ExtentForRange(28, 1)}; - new_extents = { - ExtentForRange(18, 1), ExtentForRange(20, 1), ExtentForRange(26, 1)}; - num_blocks = utils::BlocksInExtents(old_extents); - - EXPECT_EQ(num_blocks * kBlockSize, op.src_length()); - EXPECT_EQ(num_blocks * kBlockSize, op.dst_length()); - - EXPECT_EQ(old_extents.size(), static_cast<size_t>(op.src_extents_size())); - for (int i = 0; i < op.src_extents_size(); i++) { - EXPECT_EQ(old_extents[i].start_block(), op.src_extents(i).start_block()) - << "i == " << i; - EXPECT_EQ(old_extents[i].num_blocks(), op.src_extents(i).num_blocks()) - << "i == " << i; - } - - EXPECT_EQ(new_extents.size(), static_cast<size_t>(op.dst_extents_size())); - for (int i = 0; i < op.dst_extents_size(); i++) { - EXPECT_EQ(new_extents[i].start_block(), op.dst_extents(i).start_block()) - << "i == " << i; - EXPECT_EQ(new_extents[i].num_blocks(), op.dst_extents(i).num_blocks()) - << "i == " << i; - } -} - -TEST_F(DeltaDiffUtilsTest, BsdiffSmallTest) { - // Test a BSDIFF operation from block 1 to block 2. - brillo::Blob data_blob(kBlockSize); - test_utils::FillWithData(&data_blob); - - // The old file is on a different block than the new one. - vector<Extent> old_extents = {ExtentForRange(1, 1)}; - vector<Extent> new_extents = {ExtentForRange(2, 1)}; - - EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob)); - // Modify one byte in the new file. - data_blob[0]++; - EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob)); - - brillo::Blob data; - InstallOperation op; - EXPECT_TRUE(diff_utils::ReadExtentsToDiff( - old_part_.path, - new_part_.path, - old_extents, - new_extents, - {}, // old_deflates - {}, // new_deflates - PayloadVersion(kChromeOSMajorPayloadVersion, kInPlaceMinorPayloadVersion), - &data, - &op)); - - EXPECT_FALSE(data.empty()); - - EXPECT_TRUE(op.has_type()); - EXPECT_EQ(InstallOperation::BSDIFF, op.type()); - EXPECT_FALSE(op.has_data_offset()); - EXPECT_FALSE(op.has_data_length()); - EXPECT_EQ(1, op.src_extents_size()); - EXPECT_EQ(kBlockSize, op.src_length()); - EXPECT_EQ(1, op.dst_extents_size()); - EXPECT_EQ(kBlockSize, op.dst_length()); - EXPECT_EQ(utils::BlocksInExtents(op.src_extents()), - utils::BlocksInExtents(op.dst_extents())); - EXPECT_EQ(1U, utils::BlocksInExtents(op.dst_extents())); -} - TEST_F(DeltaDiffUtilsTest, ReplaceSmallTest) { // The old file is on a different block than the new one. vector<Extent> old_extents = {ExtentForRange(1, 1)}; @@ -383,8 +215,7 @@ TEST_F(DeltaDiffUtilsTest, ReplaceSmallTest) { new_extents, {}, // old_deflates {}, // new_deflates - PayloadVersion(kChromeOSMajorPayloadVersion, - kInPlaceMinorPayloadVersion), + PayloadVersion(kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion), &data, &op)); EXPECT_FALSE(data.empty()); @@ -426,7 +257,7 @@ TEST_F(DeltaDiffUtilsTest, SourceCopyTest) { new_extents, {}, // old_deflates {}, // new_deflates - PayloadVersion(kChromeOSMajorPayloadVersion, kSourceMinorPayloadVersion), + PayloadVersion(kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion), &data, &op)); EXPECT_TRUE(data.empty()); @@ -460,7 +291,7 @@ TEST_F(DeltaDiffUtilsTest, SourceBsdiffTest) { new_extents, {}, // old_deflates {}, // new_deflates - PayloadVersion(kChromeOSMajorPayloadVersion, kSourceMinorPayloadVersion), + PayloadVersion(kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion), &data, &op)); @@ -500,49 +331,6 @@ TEST_F(DeltaDiffUtilsTest, PreferReplaceTest) { EXPECT_EQ(InstallOperation::REPLACE_BZ, op.type()); } -TEST_F(DeltaDiffUtilsTest, IsNoopOperationTest) { - InstallOperation op; - op.set_type(InstallOperation::REPLACE_BZ); - EXPECT_FALSE(diff_utils::IsNoopOperation(op)); - op.set_type(InstallOperation::MOVE); - EXPECT_TRUE(diff_utils::IsNoopOperation(op)); - *(op.add_src_extents()) = ExtentForRange(3, 2); - *(op.add_dst_extents()) = ExtentForRange(3, 2); - EXPECT_TRUE(diff_utils::IsNoopOperation(op)); - *(op.add_src_extents()) = ExtentForRange(7, 5); - *(op.add_dst_extents()) = ExtentForRange(7, 5); - EXPECT_TRUE(diff_utils::IsNoopOperation(op)); - *(op.add_src_extents()) = ExtentForRange(20, 2); - *(op.add_dst_extents()) = ExtentForRange(20, 1); - *(op.add_dst_extents()) = ExtentForRange(21, 1); - EXPECT_TRUE(diff_utils::IsNoopOperation(op)); - *(op.add_src_extents()) = ExtentForRange(24, 1); - *(op.add_dst_extents()) = ExtentForRange(25, 1); - EXPECT_FALSE(diff_utils::IsNoopOperation(op)); -} - -TEST_F(DeltaDiffUtilsTest, FilterNoopOperations) { - AnnotatedOperation aop1; - aop1.op.set_type(InstallOperation::REPLACE_BZ); - *(aop1.op.add_dst_extents()) = ExtentForRange(3, 2); - aop1.name = "aop1"; - - AnnotatedOperation aop2 = aop1; - aop2.name = "aop2"; - - AnnotatedOperation noop; - noop.op.set_type(InstallOperation::MOVE); - *(noop.op.add_src_extents()) = ExtentForRange(3, 2); - *(noop.op.add_dst_extents()) = ExtentForRange(3, 2); - noop.name = "noop"; - - vector<AnnotatedOperation> ops = {noop, aop1, noop, noop, aop2, noop}; - diff_utils::FilterNoopOperations(&ops); - EXPECT_EQ(2u, ops.size()); - EXPECT_EQ("aop1", ops[0].name); - EXPECT_EQ("aop2", ops[1].name); -} - // Test the simple case where all the blocks are different and no new blocks are // zeroed. TEST_F(DeltaDiffUtilsTest, NoZeroedOrUniqueBlocksDetected) { @@ -550,7 +338,7 @@ TEST_F(DeltaDiffUtilsTest, NoZeroedOrUniqueBlocksDetected) { InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42); EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1, // chunk_blocks - kInPlaceMinorPayloadVersion)); + kSourceMinorPayloadVersion)); EXPECT_EQ(0U, old_visited_blocks_.blocks()); EXPECT_EQ(0U, new_visited_blocks_.blocks()); @@ -558,29 +346,6 @@ TEST_F(DeltaDiffUtilsTest, NoZeroedOrUniqueBlocksDetected) { EXPECT_TRUE(aops_.empty()); } -// Test that when the partitions have identical blocks in the same positions no -// MOVE operation is performed and all the blocks are handled. -TEST_F(DeltaDiffUtilsTest, IdenticalPartitionsDontMove) { - InitializePartitionWithUniqueBlocks(old_part_, block_size_, 42); - InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42); - - // Mark some of the blocks as already visited. - vector<Extent> already_visited = {ExtentForRange(5, 10), - ExtentForRange(25, 10)}; - old_visited_blocks_.AddExtents(already_visited); - new_visited_blocks_.AddExtents(already_visited); - - // Most of the blocks rest in the same place, but there's no need for MOVE - // operations on those blocks. - EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1, // chunk_blocks - kInPlaceMinorPayloadVersion)); - - EXPECT_EQ(kDefaultBlockCount, old_visited_blocks_.blocks()); - EXPECT_EQ(kDefaultBlockCount, new_visited_blocks_.blocks()); - EXPECT_EQ(0, blob_size_); - EXPECT_TRUE(aops_.empty()); -} - // Test that when the partitions have identical blocks in the same positions // MOVE operation is performed and all the blocks are handled. TEST_F(DeltaDiffUtilsTest, IdenticalBlocksAreCopiedFromSource) { @@ -701,16 +466,14 @@ TEST_F(DeltaDiffUtilsTest, ZeroBlocksUseReplaceBz) { EXPECT_TRUE(WriteExtents(old_part_.path, old_zeros, block_size_, zeros_data)); EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(5, // chunk_blocks - kInPlaceMinorPayloadVersion)); + kSourceMinorPayloadVersion)); - // Zeroed blocks from old_visited_blocks_ were copied over, so me actually - // use them regardless of the trivial MOVE operation not being emitted. + // Zeroed blocks from |old_visited_blocks_| were copied over. EXPECT_EQ(old_zeros, old_visited_blocks_.GetExtentsForBlockCount( old_visited_blocks_.blocks())); - // All the new zeroed blocks should be used, part with REPLACE_BZ and part - // trivial MOVE operations (not included). + // All the new zeroed blocks should be used with REPLACE_BZ. EXPECT_EQ(new_zeros, new_visited_blocks_.GetExtentsForBlockCount( new_visited_blocks_.blocks())); @@ -721,7 +484,8 @@ TEST_F(DeltaDiffUtilsTest, ZeroBlocksUseReplaceBz) { // This range should be split. ExtentForRange(30, 5), ExtentForRange(35, 5), - ExtentForRange(40, 3), + ExtentForRange(40, 5), + ExtentForRange(45, 5), }; EXPECT_EQ(expected_op_extents.size(), aops_.size()); @@ -821,6 +585,8 @@ TEST_F(DeltaDiffUtilsTest, GetOldFileTest) { "update_engine"); EXPECT_EQ(diff_utils::GetOldFile(old_files_map, "bin/delta_generator").name, "delta_generator"); + // Check file name with minimum size. + EXPECT_EQ(diff_utils::GetOldFile(old_files_map, "a").name, "filename"); } } // namespace chromeos_update_engine diff --git a/payload_generator/ext2_filesystem_unittest.cc b/payload_generator/ext2_filesystem_unittest.cc index 54600e90..88e15383 100644 --- a/payload_generator/ext2_filesystem_unittest.cc +++ b/payload_generator/ext2_filesystem_unittest.cc @@ -62,7 +62,7 @@ void ExpectBlocksInRange(const vector<Extent>& extents, uint64_t total_blocks) { class Ext2FilesystemTest : public ::testing::Test {}; TEST_F(Ext2FilesystemTest, InvalidFilesystem) { - test_utils::ScopedTempFile fs_filename_{"Ext2FilesystemTest-XXXXXX"}; + ScopedTempFile fs_filename_{"Ext2FilesystemTest-XXXXXX"}; ASSERT_EQ(0, truncate(fs_filename_.path().c_str(), kDefaultFilesystemSize)); unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(fs_filename_.path()); diff --git a/payload_generator/extent_ranges.cc b/payload_generator/extent_ranges.cc index 0e3f087e..2098639b 100644 --- a/payload_generator/extent_ranges.cc +++ b/payload_generator/extent_ranges.cc @@ -27,7 +27,6 @@ #include "update_engine/payload_consumer/payload_constants.h" #include "update_engine/payload_generator/extent_utils.h" -using std::set; using std::vector; namespace chromeos_update_engine { @@ -203,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/extent_ranges_unittest.cc b/payload_generator/extent_ranges_unittest.cc index 2bcffed2..f55bb737 100644 --- a/payload_generator/extent_ranges_unittest.cc +++ b/payload_generator/extent_ranges_unittest.cc @@ -18,6 +18,7 @@ #include <vector> +#include <base/stl_util.h> #include <gtest/gtest.h> #include "update_engine/common/test_utils.h" @@ -51,9 +52,9 @@ void ExpectRangeEq(const ExtentRanges& ranges, } } -#define EXPECT_RANGE_EQ(ranges, var) \ - do { \ - ExpectRangeEq(ranges, var, arraysize(var), __LINE__); \ +#define EXPECT_RANGE_EQ(ranges, var) \ + do { \ + ExpectRangeEq(ranges, var, base::size(var), __LINE__); \ } while (0) void ExpectRangesOverlapOrTouch(uint64_t a_start, diff --git a/payload_generator/extent_utils.cc b/payload_generator/extent_utils.cc index c0c76431..2efef120 100644 --- a/payload_generator/extent_utils.cc +++ b/payload_generator/extent_utils.cc @@ -155,4 +155,10 @@ bool operator==(const Extent& a, const Extent& b) { return a.start_block() == b.start_block() && a.num_blocks() == b.num_blocks(); } +std::ostream& operator<<(std::ostream& out, const Extent& extent) { + out << "[" << extent.start_block() << " - " + << extent.start_block() + extent.num_blocks() - 1 << "]"; + return out; +} + } // namespace chromeos_update_engine diff --git a/payload_generator/extent_utils.h b/payload_generator/extent_utils.h index 9763b1f1..7aa614a1 100644 --- a/payload_generator/extent_utils.h +++ b/payload_generator/extent_utils.h @@ -20,6 +20,8 @@ #include <string> #include <vector> +#include <base/logging.h> + #include "update_engine/payload_consumer/payload_constants.h" #include "update_engine/update_metadata.pb.h" @@ -83,6 +85,45 @@ std::vector<Extent> ExtentsSublist(const std::vector<Extent>& extents, bool operator==(const Extent& a, const Extent& b); +// TODO(zhangkelvin) This is ugly. Rewrite using C++20's coroutine once +// that's available. Unfortunately with C++17 this is the best I could do. + +// An iterator that takes a sequence of extents, and iterate over blocks +// inside this sequence of extents. +// Example usage: + +// BlockIterator it1{src_extents}; +// while(!it1.is_end()) { +// auto block = *it1; +// Do stuff with |block| +// } +struct BlockIterator { + explicit BlockIterator( + const google::protobuf::RepeatedPtrField<Extent>& src_extents) + : src_extents_(src_extents) {} + + BlockIterator& operator++() { + CHECK_LT(cur_extent_, src_extents_.size()); + block_offset_++; + if (block_offset_ >= src_extents_[cur_extent_].num_blocks()) { + cur_extent_++; + block_offset_ = 0; + } + return *this; + } + + [[nodiscard]] bool is_end() { return cur_extent_ >= src_extents_.size(); } + [[nodiscard]] uint64_t operator*() { + return src_extents_[cur_extent_].start_block() + block_offset_; + } + + const google::protobuf::RepeatedPtrField<Extent>& src_extents_; + int cur_extent_ = 0; + size_t block_offset_ = 0; +}; + +std::ostream& operator<<(std::ostream& out, const Extent& extent); + } // namespace chromeos_update_engine #endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_UTILS_H_ diff --git a/payload_generator/filesystem_interface.h b/payload_generator/filesystem_interface.h index d04295cd..05d387f6 100644 --- a/payload_generator/filesystem_interface.h +++ b/payload_generator/filesystem_interface.h @@ -62,6 +62,13 @@ class FilesystemInterface { // indicating the starting block, and the number of consecutive blocks. std::vector<Extent> extents; + // If true, the file is already compressed on the disk, so we don't need to + // parse it again for deflates. For example, image .gz files inside a + // compressed SquashFS image. They might have already been compressed by the + // mksquashfs, so we can't really parse the file and look for deflate + // compressed parts anymore. + bool is_compressed = false; + // All the deflate locations in the file. These locations are not relative // to the extents. They are relative to the file system itself. std::vector<puffin::BitExtent> deflates; diff --git a/payload_generator/full_update_generator.cc b/payload_generator/full_update_generator.cc index 94a43ab7..4a5f63a9 100644 --- a/payload_generator/full_update_generator.cc +++ b/payload_generator/full_update_generator.cc @@ -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/full_update_generator_unittest.cc b/payload_generator/full_update_generator_unittest.cc index e3981257..d3b3491d 100644 --- a/payload_generator/full_update_generator_unittest.cc +++ b/payload_generator/full_update_generator_unittest.cc @@ -41,11 +41,9 @@ class FullUpdateGeneratorTest : public ::testing::Test { config_.block_size = 4096; new_part_conf.path = part_file_.path(); - EXPECT_TRUE(utils::MakeTempFile( - "FullUpdateTest_blobs.XXXXXX", &out_blobs_path_, &out_blobs_fd_)); - blob_file_.reset(new BlobFileWriter(out_blobs_fd_, &out_blobs_length_)); - out_blobs_unlinker_.reset(new ScopedPathUnlinker(out_blobs_path_)); + blob_file_writer_.reset( + new BlobFileWriter(blob_file_.fd(), &out_blobs_length_)); } PayloadGenerationConfig config_; @@ -54,14 +52,11 @@ class FullUpdateGeneratorTest : public ::testing::Test { vector<AnnotatedOperation> aops; // Output file holding the payload blobs. - string out_blobs_path_; - int out_blobs_fd_{-1}; off_t out_blobs_length_{0}; - ScopedFdCloser out_blobs_fd_closer_{&out_blobs_fd_}; - test_utils::ScopedTempFile part_file_{"FullUpdateTest_partition.XXXXXX"}; + ScopedTempFile part_file_{"FullUpdateTest_partition.XXXXXX"}; - std::unique_ptr<BlobFileWriter> blob_file_; - std::unique_ptr<ScopedPathUnlinker> out_blobs_unlinker_; + ScopedTempFile blob_file_{"FullUpdateTest_blobs.XXXXXX", true}; + std::unique_ptr<BlobFileWriter> blob_file_writer_; // FullUpdateGenerator under test. FullUpdateGenerator generator_; @@ -77,7 +72,7 @@ TEST_F(FullUpdateGeneratorTest, RunTest) { EXPECT_TRUE(generator_.GenerateOperations(config_, new_part_conf, // this is ignored new_part_conf, - blob_file_.get(), + blob_file_writer_.get(), &aops)); int64_t new_part_chunks = new_part_conf.size / config_.hard_chunk_size; EXPECT_EQ(new_part_chunks, static_cast<int64_t>(aops.size())); @@ -90,7 +85,7 @@ TEST_F(FullUpdateGeneratorTest, RunTest) { EXPECT_EQ(config_.hard_chunk_size / config_.block_size, aops[i].op.dst_extents(0).num_blocks()); if (aops[i].op.type() != InstallOperation::REPLACE) { - EXPECT_EQ(InstallOperation::REPLACE_BZ, aops[i].op.type()); + EXPECT_EQ(InstallOperation::REPLACE_XZ, aops[i].op.type()); } } } @@ -108,7 +103,7 @@ TEST_F(FullUpdateGeneratorTest, ChunkSizeTooBig) { EXPECT_TRUE(generator_.GenerateOperations(config_, new_part_conf, // this is ignored new_part_conf, - blob_file_.get(), + blob_file_writer_.get(), &aops)); // new_part has one chunk and a half. EXPECT_EQ(2U, aops.size()); @@ -129,7 +124,7 @@ TEST_F(FullUpdateGeneratorTest, ImageSizeTooSmall) { EXPECT_TRUE(generator_.GenerateOperations(config_, new_part_conf, // this is ignored new_part_conf, - blob_file_.get(), + blob_file_writer_.get(), &aops)); // new_part has less than one chunk. diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc index f035ff16..b04fec0b 100644 --- a/payload_generator/generate_delta_main.cc +++ b/payload_generator/generate_delta_main.cc @@ -14,30 +14,34 @@ // limitations under the License. // +#include <map> #include <string> #include <vector> +#include <base/bind.h> #include <base/files/file_path.h> #include <base/files/file_util.h> #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> #include <xz.h> +#include "update_engine/common/download_action.h" #include "update_engine/common/fake_boot_control.h" #include "update_engine/common/fake_hardware.h" #include "update_engine/common/file_fetcher.h" #include "update_engine/common/prefs.h" #include "update_engine/common/terminator.h" #include "update_engine/common/utils.h" -#include "update_engine/payload_consumer/download_action.h" #include "update_engine/payload_consumer/filesystem_verifier_action.h" #include "update_engine/payload_consumer/payload_constants.h" #include "update_engine/payload_generator/delta_diff_generator.h" #include "update_engine/payload_generator/payload_generation_config.h" +#include "update_engine/payload_generator/payload_properties.h" #include "update_engine/payload_generator/payload_signer.h" #include "update_engine/payload_generator/xz.h" #include "update_engine/update_metadata.pb.h" @@ -46,6 +50,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; @@ -53,6 +58,9 @@ namespace chromeos_update_engine { namespace { +constexpr char kPayloadPropertiesFormatKeyValue[] = "key-value"; +constexpr char kPayloadPropertiesFormatJson[] = "json"; + void ParseSignatureSizes(const string& signature_sizes_flag, vector<size_t>* signature_sizes) { signature_sizes->clear(); @@ -67,38 +75,6 @@ void ParseSignatureSizes(const string& signature_sizes_flag, } } -bool ParseImageInfo(const string& channel, - const string& board, - const string& version, - const string& key, - const string& build_channel, - const string& build_version, - ImageInfo* image_info) { - // All of these arguments should be present or missing. - bool empty = channel.empty(); - - CHECK_EQ(channel.empty(), empty); - CHECK_EQ(board.empty(), empty); - CHECK_EQ(version.empty(), empty); - CHECK_EQ(key.empty(), empty); - - if (empty) - return false; - - image_info->set_channel(channel); - image_info->set_board(board); - image_info->set_version(version); - image_info->set_key(key); - - image_info->set_build_channel(build_channel.empty() ? channel - : build_channel); - - image_info->set_build_version(build_version.empty() ? version - : build_version); - - return true; -} - void CalculateHashForSigning(const vector<size_t>& sizes, const string& out_hash_file, const string& out_metadata_hash_file, @@ -207,8 +183,11 @@ bool ApplyPayload(const string& payload_file, install_plan.source_slot = config.is_delta ? 0 : BootControlInterface::kInvalidSlot; install_plan.target_slot = 1; - payload.type = - config.is_delta ? InstallPayloadType::kDelta : InstallPayloadType::kFull; + // For partial updates, we always write kDelta to the payload. Make it + // consistent for host simulation. + payload.type = config.is_delta || config.is_partial_update + ? InstallPayloadType::kDelta + : InstallPayloadType::kFull; payload.size = utils::FileSize(payload_file); // TODO(senj): This hash is only correct for unsigned payload, need to support // signed payload using PayloadSigner. @@ -245,11 +224,10 @@ bool ApplyPayload(const string& payload_file, std::make_unique<DownloadAction>(&prefs, &fake_boot_control, &fake_hardware, - 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()); @@ -259,7 +237,9 @@ bool ApplyPayload(const string& payload_file, processor.EnqueueAction(std::move(install_plan_action)); processor.EnqueueAction(std::move(download_action)); processor.EnqueueAction(std::move(filesystem_verifier_action)); - processor.StartProcessing(); + loop.PostTask(FROM_HERE, + base::Bind(&ActionProcessor::StartProcessing, + base::Unretained(&processor))); loop.Run(); CHECK_EQ(delegate.code_, ErrorCode::kSuccess); LOG(INFO) << "Completed applying " << (config.is_delta ? "delta" : "full") @@ -267,19 +247,62 @@ bool ApplyPayload(const string& payload_file, return true; } -int ExtractProperties(const string& payload_path, const string& props_file) { - brillo::KeyValueStore properties; - TEST_AND_RETURN_FALSE( - PayloadSigner::ExtractPayloadProperties(payload_path, &properties)); +bool ExtractProperties(const string& payload_path, + const string& props_file, + const string& props_format) { + string properties; + PayloadProperties payload_props(payload_path); + if (props_format == kPayloadPropertiesFormatKeyValue) { + TEST_AND_RETURN_FALSE(payload_props.GetPropertiesAsKeyValue(&properties)); + } else if (props_format == kPayloadPropertiesFormatJson) { + TEST_AND_RETURN_FALSE(payload_props.GetPropertiesAsJson(&properties)); + } else { + LOG(FATAL) << "Invalid option " << props_format + << " for --properties_format flag."; + } if (props_file == "-") { - printf("%s", properties.SaveToString().c_str()); + printf("%s", properties.c_str()); } else { - properties.Save(base::FilePath(props_file)); + utils::WriteFile( + props_file.c_str(), properties.c_str(), properties.length()); LOG(INFO) << "Generated properties file at " << props_file; } 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"); @@ -361,57 +384,21 @@ int Main(int argc, char** argv) { DEFINE_string(properties_file, "", "If passed, dumps the payload properties of the payload passed " - "in --in_file and exits."); + "in --in_file and exits. Look at --properties_format."); + DEFINE_string(properties_format, + kPayloadPropertiesFormatKeyValue, + "Defines the format of the --properties_file. The acceptable " + "values are: key-value (default) and json"); DEFINE_int64(max_timestamp, 0, "The maximum timestamp of the OS allowed to apply this " "payload."); - - DEFINE_string(old_channel, - "", - "The channel for the old image. 'dev-channel', 'npo-channel', " - "etc. Ignored, except during delta generation."); - DEFINE_string(old_board, - "", - "The board for the old image. 'x86-mario', 'lumpy', " - "etc. Ignored, except during delta generation."); DEFINE_string( - old_version, "", "The build version of the old image. 1.2.3, etc."); - DEFINE_string(old_key, - "", - "The key used to sign the old image. 'premp', 'mp', 'mp-v3'," - " etc"); - DEFINE_string(old_build_channel, - "", - "The channel for the build of the old image. 'dev-channel', " - "etc, but will never contain special channels such as " - "'npo-channel'. Ignored, except during delta generation."); - DEFINE_string(old_build_version, - "", - "The version of the build containing the old image."); + 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(new_channel, - "", - "The channel for the new image. 'dev-channel', 'npo-channel', " - "etc. Ignored, except during delta generation."); - DEFINE_string(new_board, - "", - "The board for the new image. 'x86-mario', 'lumpy', " - "etc. Ignored, except during delta generation."); - DEFINE_string( - new_version, "", "The build version of the new image. 1.2.3, etc."); - DEFINE_string(new_key, - "", - "The key used to sign the new image. 'premp', 'mp', 'mp-v3'," - " etc"); - DEFINE_string(new_build_channel, - "", - "The channel for the build of the new image. 'dev-channel', " - "etc, but will never contain special channels such as " - "'npo-channel'. Ignored, except during delta generation."); - DEFINE_string(new_build_version, - "", - "The version of the build containing the new image."); DEFINE_string(new_postinstall_config_file, "", "A config file specifying postinstall related metadata. " @@ -423,10 +410,23 @@ int Main(int argc, char** argv) { DEFINE_bool(disable_fec_computation, false, "Disables the fec data computation on device."); + DEFINE_bool(disable_verity_computation, + false, + "Disables the verity 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."); + DEFINE_bool( + disable_vabc, + false, + "Whether to disable Virtual AB Compression when installing the OTA"); + DEFINE_string( + apex_info_file, "", "Path to META/apex_info.pb found in target build"); brillo::FlagHelper::Init( argc, @@ -438,7 +438,11 @@ int Main(int argc, char** argv) { Terminator::Init(); logging::LoggingSettings log_settings; +#if BASE_VER < 780000 log_settings.log_file = "delta_generator.log"; +#else + log_settings.log_file_path = "delta_generator.log"; +#endif log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; log_settings.lock_log = logging::LOCK_LOG_FILE; log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE; @@ -500,7 +504,10 @@ int Main(int argc, char** argv) { return VerifySignedPayload(FLAGS_in_file, FLAGS_public_key); } if (!FLAGS_properties_file.empty()) { - return ExtractProperties(FLAGS_in_file, FLAGS_properties_file) ? 0 : 1; + return ExtractProperties( + FLAGS_in_file, FLAGS_properties_file, FLAGS_properties_format) + ? 0 + : 1; } // A payload generation was requested. Convert the flags to a @@ -521,16 +528,19 @@ int Main(int argc, char** argv) { partition_names = base::SplitString( FLAGS_partition_names, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); CHECK(!partition_names.empty()); - if (FLAGS_major_version == kChromeOSMajorPayloadVersion || - FLAGS_new_partitions.empty()) { - LOG_IF(FATAL, partition_names.size() != 2) - << "To support more than 2 partitions, please use the " - << "--new_partitions flag and major version 2."; - LOG_IF(FATAL, - partition_names[0] != kPartitionNameRoot || - partition_names[1] != kPartitionNameKernel) - << "To support non-default partition name, please use the " - << "--new_partitions flag and major version 2."; + if (FLAGS_major_version < kMinSupportedMajorPayloadVersion || + FLAGS_major_version > kMaxSupportedMajorPayloadVersion) { + LOG(FATAL) << "Unsupported major version " << FLAGS_major_version; + return 1; + } + + if (!FLAGS_apex_info_file.empty()) { + // apex_info_file should point to a regular file(or symlink to a regular + // file) + CHECK(utils::FileExists(FLAGS_apex_info_file.c_str())); + CHECK(utils::IsRegFile(FLAGS_apex_info_file.c_str()) || + utils::IsSymlink(FLAGS_apex_info_file.c_str())); + payload_config.apex_info_file = FLAGS_apex_info_file; } if (!FLAGS_new_partitions.empty()) { @@ -586,13 +596,15 @@ int Main(int argc, char** argv) { } } + if (FLAGS_is_partial_update) { + payload_config.is_partial_update = true; + } + if (!FLAGS_in_file.empty()) { return ApplyPayload(FLAGS_in_file, payload_config) ? 0 : 1; } if (!FLAGS_new_postinstall_config_file.empty()) { - LOG_IF(FATAL, FLAGS_major_version == kChromeOSMajorPayloadVersion) - << "Postinstall config is only allowed in major version 2 or newer."; brillo::KeyValueStore store; CHECK(store.Load(base::FilePath(FLAGS_new_postinstall_config_file))); CHECK(payload_config.target.LoadPostInstallConfig(store)); @@ -610,35 +622,20 @@ int Main(int argc, char** argv) { CHECK(payload_config.target.LoadImageSize()); if (!FLAGS_dynamic_partition_info_file.empty()) { - LOG_IF(FATAL, FLAGS_major_version == kChromeOSMajorPayloadVersion) - << "Dynamic partition info is only allowed in major version 2 or " - "newer."; brillo::KeyValueStore store; CHECK(store.Load(base::FilePath(FLAGS_dynamic_partition_info_file))); CHECK(payload_config.target.LoadDynamicPartitionMetadata(store)); CHECK(payload_config.target.ValidateDynamicPartitionMetadata()); + if (FLAGS_disable_vabc) { + LOG(INFO) << "Disabling VABC"; + payload_config.target.dynamic_partition_metadata->set_vabc_enabled(false); + payload_config.target.dynamic_partition_metadata + ->set_vabc_compression_param(""); + } } CHECK(!FLAGS_out_file.empty()); - // Ignore failures. These are optional arguments. - ParseImageInfo(FLAGS_new_channel, - FLAGS_new_board, - FLAGS_new_version, - FLAGS_new_key, - FLAGS_new_build_channel, - FLAGS_new_build_version, - &payload_config.target.image_info); - - // Ignore failures. These are optional arguments. - ParseImageInfo(FLAGS_old_channel, - FLAGS_old_board, - FLAGS_old_version, - FLAGS_old_key, - FLAGS_old_build_channel, - FLAGS_old_build_version, - &payload_config.source.image_info); - payload_config.rootfs_partition_size = FLAGS_rootfs_partition_size; if (payload_config.is_delta) { @@ -656,28 +653,49 @@ int Main(int argc, char** argv) { // Autodetect minor_version by looking at the update_engine.conf in the old // image. if (payload_config.is_delta) { - payload_config.version.minor = kInPlaceMinorPayloadVersion; brillo::KeyValueStore store; uint32_t minor_version; + bool minor_version_found = false; for (const PartitionConfig& part : payload_config.source.partitions) { if (part.fs_interface && part.fs_interface->LoadSettings(&store) && utils::GetMinorVersion(store, &minor_version)) { payload_config.version.minor = minor_version; + minor_version_found = true; + LOG(INFO) << "Auto-detected minor_version=" + << payload_config.version.minor; break; } } + if (!minor_version_found) { + LOG(FATAL) << "Failed to detect the minor version."; + return 1; + } } else { payload_config.version.minor = kFullPayloadMinorVersion; + LOG(INFO) << "Using non-delta minor_version=" + << payload_config.version.minor; } - LOG(INFO) << "Auto-detected minor_version=" << payload_config.version.minor; } else { payload_config.version.minor = FLAGS_minor_version; LOG(INFO) << "Using provided minor_version=" << FLAGS_minor_version; } + if (payload_config.version.minor != kFullPayloadMinorVersion && + (payload_config.version.minor < kMinSupportedMinorPayloadVersion || + payload_config.version.minor > kMaxSupportedMinorPayloadVersion)) { + LOG(FATAL) << "Unsupported minor version " << payload_config.version.minor; + return 1; + } + 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 && + !FLAGS_disable_verity_computation) CHECK(payload_config.target.LoadVerityConfig()); LOG(INFO) << "Generating " << (payload_config.is_delta ? "delta" : "full") diff --git a/payload_generator/graph_types.h b/payload_generator/graph_types.h deleted file mode 100644 index f96b0f38..00000000 --- a/payload_generator/graph_types.h +++ /dev/null @@ -1,87 +0,0 @@ -// -// Copyright (C) 2009 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_TYPES_H_ -#define UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_TYPES_H_ - -#include <map> -#include <set> -#include <string> -#include <utility> -#include <vector> - -#include <base/macros.h> - -#include "update_engine/payload_generator/annotated_operation.h" -#include "update_engine/payload_generator/extent_utils.h" -#include "update_engine/update_metadata.pb.h" - -// A few classes that help in generating delta images use these types -// for the graph work. - -namespace chromeos_update_engine { - -struct EdgeProperties { - // Read-before extents. I.e., blocks in |extents| must be read by the - // node pointed to before the pointing node runs (presumably b/c it - // overwrites these blocks). - std::vector<Extent> extents; - - // Write before extents. I.e., blocks in |write_extents| must be written - // by the node pointed to before the pointing node runs (presumably - // b/c it reads the data written by the other node). - std::vector<Extent> write_extents; - - bool operator==(const EdgeProperties& that) const { - return extents == that.extents && write_extents == that.write_extents; - } -}; - -struct Vertex { - Vertex() : valid(true), index(-1), lowlink(-1) {} - bool valid; - - typedef std::map<std::vector<Vertex>::size_type, EdgeProperties> EdgeMap; - EdgeMap out_edges; - - // We sometimes wish to consider a subgraph of a graph. A subgraph would have - // a subset of the vertices from the graph and a subset of the edges. - // When considering this vertex within a subgraph, subgraph_edges stores - // the out-edges. - typedef std::set<std::vector<Vertex>::size_type> SubgraphEdgeMap; - SubgraphEdgeMap subgraph_edges; - - // For Tarjan's algorithm: - std::vector<Vertex>::size_type index; - std::vector<Vertex>::size_type lowlink; - - // Other Vertex properties: - AnnotatedOperation aop; - - typedef std::vector<Vertex>::size_type Index; - static const Vertex::Index kInvalidIndex; -}; - -typedef std::vector<Vertex> Graph; - -typedef std::pair<Vertex::Index, Vertex::Index> Edge; - -const uint64_t kTempBlockStart = 1ULL << 60; -static_assert(kTempBlockStart != 0, "kTempBlockStart invalid"); - -} // namespace chromeos_update_engine - -#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_TYPES_H_ diff --git a/payload_generator/graph_utils.cc b/payload_generator/graph_utils.cc deleted file mode 100644 index 7f5cf8fb..00000000 --- a/payload_generator/graph_utils.cc +++ /dev/null @@ -1,142 +0,0 @@ -// -// Copyright (C) 2009 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include "update_engine/payload_generator/graph_utils.h" - -#include <string> -#include <utility> -#include <vector> - -#include <base/logging.h> -#include <base/macros.h> - -#include "update_engine/payload_consumer/payload_constants.h" -#include "update_engine/payload_generator/annotated_operation.h" -#include "update_engine/payload_generator/extent_utils.h" - -using std::make_pair; -using std::pair; -using std::string; -using std::vector; - -namespace chromeos_update_engine { -namespace graph_utils { - -uint64_t EdgeWeight(const Graph& graph, const Edge& edge) { - uint64_t weight = 0; - const vector<Extent>& extents = - graph[edge.first].out_edges.find(edge.second)->second.extents; - for (vector<Extent>::const_iterator it = extents.begin(); it != extents.end(); - ++it) { - if (it->start_block() != kSparseHole) - weight += it->num_blocks(); - } - return weight; -} - -void AddReadBeforeDep(Vertex* src, Vertex::Index dst, uint64_t block) { - Vertex::EdgeMap::iterator edge_it = src->out_edges.find(dst); - if (edge_it == src->out_edges.end()) { - // Must create new edge - pair<Vertex::EdgeMap::iterator, bool> result = - src->out_edges.insert(make_pair(dst, EdgeProperties())); - CHECK(result.second); - edge_it = result.first; - } - AppendBlockToExtents(&edge_it->second.extents, block); -} - -void AddReadBeforeDepExtents(Vertex* src, - Vertex::Index dst, - const vector<Extent>& extents) { - // TODO(adlr): Be more efficient than adding each block individually. - for (vector<Extent>::const_iterator it = extents.begin(), e = extents.end(); - it != e; - ++it) { - const Extent& extent = *it; - for (uint64_t block = extent.start_block(), - block_end = extent.start_block() + extent.num_blocks(); - block != block_end; - ++block) { - AddReadBeforeDep(src, dst, block); - } - } -} - -void DropWriteBeforeDeps(Vertex::EdgeMap* edge_map) { - // Specially crafted for-loop for the map-iterate-delete dance. - for (Vertex::EdgeMap::iterator it = edge_map->begin(); - it != edge_map->end();) { - if (!it->second.write_extents.empty()) - it->second.write_extents.clear(); - if (it->second.extents.empty()) { - // Erase *it, as it contains no blocks - edge_map->erase(it++); - } else { - ++it; - } - } -} - -// For each node N in graph, drop all edges N->|index|. -void DropIncomingEdgesTo(Graph* graph, Vertex::Index index) { - // This would be much more efficient if we had doubly-linked - // edges in the graph. - for (Graph::iterator it = graph->begin(), e = graph->end(); it != e; ++it) { - it->out_edges.erase(index); - } -} - -namespace { -template <typename T> -void DumpExtents(const T& field, int prepend_space_count) { - string header(prepend_space_count, ' '); - for (const auto& extent : field) { - LOG(INFO) << header << "(" << extent.start_block() << ", " - << extent.num_blocks() << ")"; - } -} - -void DumpOutEdges(const Vertex::EdgeMap& out_edges) { - for (Vertex::EdgeMap::const_iterator it = out_edges.begin(), - e = out_edges.end(); - it != e; - ++it) { - LOG(INFO) << " " << it->first << " read-before:"; - DumpExtents(it->second.extents, 6); - LOG(INFO) << " write-before:"; - DumpExtents(it->second.write_extents, 6); - } -} -} // namespace - -void DumpGraph(const Graph& graph) { - LOG(INFO) << "Graph length: " << graph.size(); - for (Graph::size_type i = 0, e = graph.size(); i != e; ++i) { - LOG(INFO) << i << (graph[i].valid ? "" : "-INV") << ": " - << graph[i].aop.name << ": " - << InstallOperationTypeName(graph[i].aop.op.type()); - LOG(INFO) << " src_extents:"; - DumpExtents(graph[i].aop.op.src_extents(), 4); - LOG(INFO) << " dst_extents:"; - DumpExtents(graph[i].aop.op.dst_extents(), 4); - LOG(INFO) << " out edges:"; - DumpOutEdges(graph[i].out_edges); - } -} - -} // namespace graph_utils -} // namespace chromeos_update_engine diff --git a/payload_generator/graph_utils.h b/payload_generator/graph_utils.h deleted file mode 100644 index 7024215d..00000000 --- a/payload_generator/graph_utils.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright (C) 2009 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_UTILS_H_ -#define UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_UTILS_H_ - -#include <vector> - -#include <base/macros.h> - -#include "update_engine/payload_generator/graph_types.h" -#include "update_engine/update_metadata.pb.h" - -// A few utility functions for graphs - -namespace chromeos_update_engine { - -namespace graph_utils { - -// Returns the number of blocks represented by all extents in the edge. -uint64_t EdgeWeight(const Graph& graph, const Edge& edge); - -// These add a read-before dependency from graph[src] -> graph[dst]. If the dep -// already exists, the block/s is/are added to the existing edge. -void AddReadBeforeDep(Vertex* src, Vertex::Index dst, uint64_t block); -void AddReadBeforeDepExtents(Vertex* src, - Vertex::Index dst, - const std::vector<Extent>& extents); - -void DropWriteBeforeDeps(Vertex::EdgeMap* edge_map); - -// For each node N in graph, drop all edges N->|index|. -void DropIncomingEdgesTo(Graph* graph, Vertex::Index index); - -void DumpGraph(const Graph& graph); - -} // namespace graph_utils - -} // namespace chromeos_update_engine - -#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_UTILS_H_ diff --git a/payload_generator/graph_utils_unittest.cc b/payload_generator/graph_utils_unittest.cc deleted file mode 100644 index 07e76646..00000000 --- a/payload_generator/graph_utils_unittest.cc +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (C) 2009 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include "update_engine/payload_generator/graph_utils.h" - -#include <utility> -#include <vector> - -#include <gtest/gtest.h> - -#include "update_engine/payload_consumer/payload_constants.h" -#include "update_engine/payload_generator/extent_ranges.h" -#include "update_engine/payload_generator/extent_utils.h" - -using std::make_pair; -using std::vector; - -namespace chromeos_update_engine { - -class GraphUtilsTest : public ::testing::Test {}; - -TEST(GraphUtilsTest, SimpleTest) { - Graph graph(2); - - graph[0].out_edges.insert(make_pair(1, EdgeProperties())); - - vector<Extent>& extents = graph[0].out_edges[1].extents; - - EXPECT_EQ(0U, extents.size()); - AppendBlockToExtents(&extents, 0); - EXPECT_EQ(1U, extents.size()); - AppendBlockToExtents(&extents, 1); - AppendBlockToExtents(&extents, 2); - EXPECT_EQ(1U, extents.size()); - AppendBlockToExtents(&extents, 4); - - EXPECT_EQ(2U, extents.size()); - EXPECT_EQ(0U, extents[0].start_block()); - EXPECT_EQ(3U, extents[0].num_blocks()); - EXPECT_EQ(4U, extents[1].start_block()); - EXPECT_EQ(1U, extents[1].num_blocks()); - - EXPECT_EQ(4U, graph_utils::EdgeWeight(graph, make_pair(0, 1))); -} - -TEST(GraphUtilsTest, DepsTest) { - Graph graph(3); - - graph_utils::AddReadBeforeDep(&graph[0], 1, 3); - EXPECT_EQ(1U, graph[0].out_edges.size()); - { - Extent& extent = graph[0].out_edges[1].extents[0]; - EXPECT_EQ(3U, extent.start_block()); - EXPECT_EQ(1U, extent.num_blocks()); - } - graph_utils::AddReadBeforeDep(&graph[0], 1, 4); - EXPECT_EQ(1U, graph[0].out_edges.size()); - { - Extent& extent = graph[0].out_edges[1].extents[0]; - EXPECT_EQ(3U, extent.start_block()); - EXPECT_EQ(2U, extent.num_blocks()); - } - graph_utils::AddReadBeforeDepExtents( - &graph[2], 1, vector<Extent>(1, ExtentForRange(5, 2))); - EXPECT_EQ(1U, graph[2].out_edges.size()); - { - Extent& extent = graph[2].out_edges[1].extents[0]; - EXPECT_EQ(5U, extent.start_block()); - EXPECT_EQ(2U, extent.num_blocks()); - } - // Change most recent edge from read-before to write-before - graph[2].out_edges[1].write_extents.swap(graph[2].out_edges[1].extents); - graph_utils::DropWriteBeforeDeps(&graph[2].out_edges); - EXPECT_EQ(0U, graph[2].out_edges.size()); - - EXPECT_EQ(1U, graph[0].out_edges.size()); - graph_utils::DropIncomingEdgesTo(&graph, 1); - EXPECT_EQ(0U, graph[0].out_edges.size()); -} - -} // namespace chromeos_update_engine diff --git a/payload_generator/inplace_generator.cc b/payload_generator/inplace_generator.cc deleted file mode 100644 index d553cc41..00000000 --- a/payload_generator/inplace_generator.cc +++ /dev/null @@ -1,798 +0,0 @@ -// -// Copyright (C) 2015 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/inplace_generator.h" - -#include <algorithm> -#include <map> -#include <set> -#include <string> -#include <utility> -#include <vector> - -#include <base/stl_util.h> - -#include "update_engine/common/utils.h" -#include "update_engine/payload_consumer/payload_constants.h" -#include "update_engine/payload_generator/cycle_breaker.h" -#include "update_engine/payload_generator/delta_diff_generator.h" -#include "update_engine/payload_generator/delta_diff_utils.h" -#include "update_engine/payload_generator/extent_ranges.h" -#include "update_engine/payload_generator/graph_types.h" -#include "update_engine/payload_generator/graph_utils.h" -#include "update_engine/payload_generator/topological_sort.h" -#include "update_engine/update_metadata.pb.h" - -using std::make_pair; -using std::map; -using std::pair; -using std::set; -using std::string; -using std::vector; - -namespace chromeos_update_engine { - -using Block = InplaceGenerator::Block; - -namespace { - -// The only PayloadVersion supported by this implementation. -const PayloadVersion kInPlacePayloadVersion{kChromeOSMajorPayloadVersion, - kInPlaceMinorPayloadVersion}; - -// This class allocates non-existent temp blocks, starting from -// kTempBlockStart. Other code is responsible for converting these -// temp blocks into real blocks, as the client can't read or write to -// these blocks. -class DummyExtentAllocator { - public: - vector<Extent> Allocate(const uint64_t block_count) { - vector<Extent> ret(1); - ret[0].set_start_block(next_block_); - ret[0].set_num_blocks(block_count); - next_block_ += block_count; - return ret; - } - - private: - uint64_t next_block_{kTempBlockStart}; -}; - -// Takes a vector of blocks and returns an equivalent vector of Extent -// objects. -vector<Extent> CompressExtents(const vector<uint64_t>& blocks) { - vector<Extent> new_extents; - for (uint64_t block : blocks) { - AppendBlockToExtents(&new_extents, block); - } - return new_extents; -} - -// Helper class to compare two operations by start block of the first Extent in -// their destination extents given the index of the operations in the graph. -class IndexedInstallOperationsDstComparator { - public: - explicit IndexedInstallOperationsDstComparator(Graph* graph) - : graph_(graph) {} - - // Compares the operations in the vertex a and b of graph_. - bool operator()(size_t a, size_t b) const { - return diff_utils::CompareAopsByDestination((*graph_)[a].aop, - (*graph_)[b].aop); - } - - private: - const Graph* const graph_; -}; - -} // namespace - -void InplaceGenerator::CheckGraph(const Graph& graph) { - for (const Vertex& v : graph) { - CHECK(v.aop.op.has_type()); - } -} - -void InplaceGenerator::SubstituteBlocks(Vertex* vertex, - const vector<Extent>& remove_extents, - const vector<Extent>& replace_extents) { - // First, expand out the blocks that op reads from - vector<uint64_t> read_blocks = ExpandExtents(vertex->aop.op.src_extents()); - { - // Expand remove_extents and replace_extents - vector<uint64_t> remove_extents_expanded = ExpandExtents(remove_extents); - vector<uint64_t> replace_extents_expanded = ExpandExtents(replace_extents); - CHECK_EQ(remove_extents_expanded.size(), replace_extents_expanded.size()); - map<uint64_t, uint64_t> conversion; - for (vector<uint64_t>::size_type i = 0; i < replace_extents_expanded.size(); - i++) { - conversion[remove_extents_expanded[i]] = replace_extents_expanded[i]; - } - ApplyMap(&read_blocks, conversion); - for (auto& edge_prop_pair : vertex->out_edges) { - vector<uint64_t> write_before_deps_expanded = - ExpandExtents(edge_prop_pair.second.write_extents); - ApplyMap(&write_before_deps_expanded, conversion); - edge_prop_pair.second.write_extents = - CompressExtents(write_before_deps_expanded); - } - } - // Convert read_blocks back to extents - vertex->aop.op.clear_src_extents(); - vector<Extent> new_extents = CompressExtents(read_blocks); - StoreExtents(new_extents, vertex->aop.op.mutable_src_extents()); -} - -bool InplaceGenerator::CutEdges(Graph* graph, - const set<Edge>& edges, - vector<CutEdgeVertexes>* out_cuts) { - DummyExtentAllocator scratch_allocator; - vector<CutEdgeVertexes> cuts; - cuts.reserve(edges.size()); - - uint64_t scratch_blocks_used = 0; - for (const Edge& edge : edges) { - cuts.resize(cuts.size() + 1); - vector<Extent> old_extents = - (*graph)[edge.first].out_edges[edge.second].extents; - // Choose some scratch space - scratch_blocks_used += graph_utils::EdgeWeight(*graph, edge); - cuts.back().tmp_extents = - scratch_allocator.Allocate(graph_utils::EdgeWeight(*graph, edge)); - // create vertex to copy original->scratch - cuts.back().new_vertex = graph->size(); - graph->emplace_back(); - cuts.back().old_src = edge.first; - cuts.back().old_dst = edge.second; - - EdgeProperties& cut_edge_properties = - (*graph)[edge.first].out_edges.find(edge.second)->second; - - // This should never happen, as we should only be cutting edges between - // real file nodes, and write-before relationships are created from - // a real file node to a temp copy node: - CHECK(cut_edge_properties.write_extents.empty()) - << "Can't cut edge that has write-before relationship."; - - // make node depend on the copy operation - (*graph)[edge.first].out_edges.insert( - make_pair(graph->size() - 1, cut_edge_properties)); - - // Set src/dst extents and other proto variables for copy operation - graph->back().aop.op.set_type(InstallOperation::MOVE); - StoreExtents(cut_edge_properties.extents, - graph->back().aop.op.mutable_src_extents()); - StoreExtents(cuts.back().tmp_extents, - graph->back().aop.op.mutable_dst_extents()); - graph->back().aop.op.set_src_length(graph_utils::EdgeWeight(*graph, edge) * - kBlockSize); - graph->back().aop.op.set_dst_length(graph->back().aop.op.src_length()); - - // make the dest node read from the scratch space - SubstituteBlocks(&((*graph)[edge.second]), - (*graph)[edge.first].out_edges[edge.second].extents, - cuts.back().tmp_extents); - - // delete the old edge - CHECK_EQ(static_cast<Graph::size_type>(1), - (*graph)[edge.first].out_edges.erase(edge.second)); - - // Add an edge from dst to copy operation - EdgeProperties write_before_edge_properties; - write_before_edge_properties.write_extents = cuts.back().tmp_extents; - (*graph)[edge.second].out_edges.insert( - make_pair(graph->size() - 1, write_before_edge_properties)); - } - out_cuts->swap(cuts); - return true; -} - -// Creates all the edges for the graph. Writers of a block point to -// readers of the same block. This is because for an edge A->B, B -// must complete before A executes. -void InplaceGenerator::CreateEdges(Graph* graph, const vector<Block>& blocks) { - for (vector<Block>::size_type i = 0; i < blocks.size(); i++) { - // Blocks with both a reader and writer get an edge - if (blocks[i].reader == Vertex::kInvalidIndex || - blocks[i].writer == Vertex::kInvalidIndex) - continue; - // Don't have a node depend on itself - if (blocks[i].reader == blocks[i].writer) - continue; - // See if there's already an edge we can add onto - Vertex::EdgeMap::iterator edge_it = - (*graph)[blocks[i].writer].out_edges.find(blocks[i].reader); - if (edge_it == (*graph)[blocks[i].writer].out_edges.end()) { - // No existing edge. Create one - (*graph)[blocks[i].writer].out_edges.insert( - make_pair(blocks[i].reader, EdgeProperties())); - edge_it = (*graph)[blocks[i].writer].out_edges.find(blocks[i].reader); - CHECK(edge_it != (*graph)[blocks[i].writer].out_edges.end()); - } - AppendBlockToExtents(&edge_it->second.extents, i); - } -} - -namespace { - -class SortCutsByTopoOrderLess { - public: - explicit SortCutsByTopoOrderLess( - const vector<vector<Vertex::Index>::size_type>& table) - : table_(table) {} - bool operator()(const CutEdgeVertexes& a, const CutEdgeVertexes& b) { - return table_[a.old_dst] < table_[b.old_dst]; - } - - private: - const vector<vector<Vertex::Index>::size_type>& table_; -}; - -} // namespace - -void InplaceGenerator::GenerateReverseTopoOrderMap( - const vector<Vertex::Index>& op_indexes, - vector<vector<Vertex::Index>::size_type>* reverse_op_indexes) { - vector<vector<Vertex::Index>::size_type> table(op_indexes.size()); - for (vector<Vertex::Index>::size_type i = 0, e = op_indexes.size(); i != e; - ++i) { - Vertex::Index node = op_indexes[i]; - if (table.size() < (node + 1)) { - table.resize(node + 1); - } - table[node] = i; - } - reverse_op_indexes->swap(table); -} - -void InplaceGenerator::SortCutsByTopoOrder( - const vector<Vertex::Index>& op_indexes, vector<CutEdgeVertexes>* cuts) { - // first, make a reverse lookup table. - vector<vector<Vertex::Index>::size_type> table; - GenerateReverseTopoOrderMap(op_indexes, &table); - SortCutsByTopoOrderLess less(table); - sort(cuts->begin(), cuts->end(), less); -} - -void InplaceGenerator::MoveAndSortFullOpsToBack( - Graph* graph, vector<Vertex::Index>* op_indexes) { - vector<Vertex::Index> ret; - vector<Vertex::Index> full_ops; - ret.reserve(op_indexes->size()); - for (auto op_index : *op_indexes) { - InstallOperation::Type type = (*graph)[op_index].aop.op.type(); - if (type == InstallOperation::REPLACE || - type == InstallOperation::REPLACE_BZ) { - full_ops.push_back(op_index); - } else { - ret.push_back(op_index); - } - } - LOG(INFO) << "Stats: " << full_ops.size() << " full ops out of " - << (full_ops.size() + ret.size()) << " total ops."; - // Sort full ops according to their dst_extents. - sort(full_ops.begin(), - full_ops.end(), - IndexedInstallOperationsDstComparator(graph)); - ret.insert(ret.end(), full_ops.begin(), full_ops.end()); - op_indexes->swap(ret); -} - -namespace { - -template <typename T> -bool TempBlocksExistInExtents(const T& extents) { - for (const auto& extent : extents) { - uint64_t start = extent.start_block(); - uint64_t num = extent.num_blocks(); - if (start >= kTempBlockStart || (start + num) >= kTempBlockStart) { - LOG(ERROR) << "temp block!"; - LOG(ERROR) << "start: " << start << ", num: " << num; - LOG(ERROR) << "kTempBlockStart: " << kTempBlockStart; - LOG(ERROR) << "returning true"; - return true; - } - // check for wrap-around, which would be a bug: - CHECK(start <= (start + num)); - } - return false; -} - -// Converts the cuts, which must all have the same |old_dst| member, -// to full. It does this by converting the |old_dst| to REPLACE or -// REPLACE_BZ, dropping all incoming edges to |old_dst|, and marking -// all temp nodes invalid. -bool ConvertCutsToFull( - Graph* graph, - const string& new_part, - BlobFileWriter* blob_file, - vector<Vertex::Index>* op_indexes, - vector<vector<Vertex::Index>::size_type>* reverse_op_indexes, - const vector<CutEdgeVertexes>& cuts) { - CHECK(!cuts.empty()); - set<Vertex::Index> deleted_nodes; - for (const CutEdgeVertexes& cut : cuts) { - TEST_AND_RETURN_FALSE( - InplaceGenerator::ConvertCutToFullOp(graph, cut, new_part, blob_file)); - deleted_nodes.insert(cut.new_vertex); - } - deleted_nodes.insert(cuts[0].old_dst); - - vector<Vertex::Index> new_op_indexes; - new_op_indexes.reserve(op_indexes->size()); - for (Vertex::Index vertex_index : *op_indexes) { - if (base::ContainsKey(deleted_nodes, vertex_index)) - continue; - new_op_indexes.push_back(vertex_index); - } - new_op_indexes.push_back(cuts[0].old_dst); - op_indexes->swap(new_op_indexes); - InplaceGenerator::GenerateReverseTopoOrderMap(*op_indexes, - reverse_op_indexes); - return true; -} - -// Tries to assign temp blocks for a collection of cuts, all of which share -// the same old_dst member. If temp blocks can't be found, old_dst will be -// converted to a REPLACE or REPLACE_BZ operation. Returns true on success, -// which can happen even if blocks are converted to full. Returns false -// on exceptional error cases. -bool AssignBlockForAdjoiningCuts( - Graph* graph, - const string& new_part, - BlobFileWriter* blob_file, - vector<Vertex::Index>* op_indexes, - vector<vector<Vertex::Index>::size_type>* reverse_op_indexes, - const vector<CutEdgeVertexes>& cuts) { - CHECK(!cuts.empty()); - const Vertex::Index old_dst = cuts[0].old_dst; - // Calculate # of blocks needed - uint64_t blocks_needed = 0; - vector<uint64_t> cuts_blocks_needed(cuts.size()); - for (vector<CutEdgeVertexes>::size_type i = 0; i < cuts.size(); ++i) { - uint64_t cut_blocks_needed = 0; - for (const Extent& extent : cuts[i].tmp_extents) { - cut_blocks_needed += extent.num_blocks(); - } - blocks_needed += cut_blocks_needed; - cuts_blocks_needed[i] = cut_blocks_needed; - } - - // Find enough blocks - ExtentRanges scratch_ranges; - // Each block that's supplying temp blocks and the corresponding blocks: - typedef vector<pair<Vertex::Index, ExtentRanges>> SupplierVector; - SupplierVector block_suppliers; - uint64_t scratch_blocks_found = 0; - for (vector<Vertex::Index>::size_type i = (*reverse_op_indexes)[old_dst] + 1, - e = op_indexes->size(); - i < e; - ++i) { - Vertex::Index test_node = (*op_indexes)[i]; - if (!(*graph)[test_node].valid) - continue; - // See if this node has sufficient blocks - ExtentRanges ranges; - ranges.AddRepeatedExtents((*graph)[test_node].aop.op.dst_extents()); - ranges.SubtractExtent( - ExtentForRange(kTempBlockStart, kSparseHole - kTempBlockStart)); - ranges.SubtractRepeatedExtents((*graph)[test_node].aop.op.src_extents()); - // For now, for simplicity, subtract out all blocks in read-before - // dependencies. - for (Vertex::EdgeMap::const_iterator - edge_i = (*graph)[test_node].out_edges.begin(), - edge_e = (*graph)[test_node].out_edges.end(); - edge_i != edge_e; - ++edge_i) { - ranges.SubtractExtents(edge_i->second.extents); - } - - // Prevent using the block 0 as scratch space due to crbug.com/480751. - if (ranges.ContainsBlock(0)) { - LOG(INFO) << "Removing block 0 from the selected scratch range in vertex " - << i; - ranges.SubtractBlock(0); - } - - if (ranges.blocks() == 0) - continue; - - if (ranges.blocks() + scratch_blocks_found > blocks_needed) { - // trim down ranges - vector<Extent> new_ranges = - ranges.GetExtentsForBlockCount(blocks_needed - scratch_blocks_found); - ranges = ExtentRanges(); - ranges.AddExtents(new_ranges); - } - scratch_ranges.AddRanges(ranges); - block_suppliers.push_back(make_pair(test_node, ranges)); - scratch_blocks_found += ranges.blocks(); - if (scratch_ranges.blocks() >= blocks_needed) - break; - } - if (scratch_ranges.blocks() < blocks_needed) { - LOG(INFO) << "Unable to find sufficient scratch"; - TEST_AND_RETURN_FALSE(ConvertCutsToFull( - graph, new_part, blob_file, op_indexes, reverse_op_indexes, cuts)); - return true; - } - // Use the scratch we found - TEST_AND_RETURN_FALSE(scratch_ranges.blocks() == scratch_blocks_found); - - // Make all the suppliers depend on this node - for (const auto& index_range_pair : block_suppliers) { - graph_utils::AddReadBeforeDepExtents( - &(*graph)[index_range_pair.first], - old_dst, - index_range_pair.second.GetExtentsForBlockCount( - index_range_pair.second.blocks())); - } - - // Replace temp blocks in each cut - for (vector<CutEdgeVertexes>::size_type i = 0; i < cuts.size(); ++i) { - const CutEdgeVertexes& cut = cuts[i]; - vector<Extent> real_extents = - scratch_ranges.GetExtentsForBlockCount(cuts_blocks_needed[i]); - scratch_ranges.SubtractExtents(real_extents); - - // Fix the old dest node w/ the real blocks - InplaceGenerator::SubstituteBlocks( - &(*graph)[old_dst], cut.tmp_extents, real_extents); - - // Fix the new node w/ the real blocks. Since the new node is just a - // copy operation, we can replace all the dest extents w/ the real - // blocks. - InstallOperation* op = &(*graph)[cut.new_vertex].aop.op; - op->clear_dst_extents(); - StoreExtents(real_extents, op->mutable_dst_extents()); - } - return true; -} - -} // namespace - -bool InplaceGenerator::AssignTempBlocks( - Graph* graph, - const string& new_part, - BlobFileWriter* blob_file, - vector<Vertex::Index>* op_indexes, - vector<vector<Vertex::Index>::size_type>* reverse_op_indexes, - const vector<CutEdgeVertexes>& cuts) { - CHECK(!cuts.empty()); - - // group of cuts w/ the same old_dst: - vector<CutEdgeVertexes> cuts_group; - - for (vector<CutEdgeVertexes>::size_type i = cuts.size() - 1, e = 0; true; - --i) { - LOG(INFO) << "Fixing temp blocks in cut " << i - << ": old dst: " << cuts[i].old_dst - << " new vertex: " << cuts[i].new_vertex - << " path: " << (*graph)[cuts[i].old_dst].aop.name; - - if (cuts_group.empty() || (cuts_group[0].old_dst == cuts[i].old_dst)) { - cuts_group.push_back(cuts[i]); - } else { - CHECK(!cuts_group.empty()); - TEST_AND_RETURN_FALSE(AssignBlockForAdjoiningCuts(graph, - new_part, - blob_file, - op_indexes, - reverse_op_indexes, - cuts_group)); - cuts_group.clear(); - cuts_group.push_back(cuts[i]); - } - - if (i == e) { - // break out of for() loop - break; - } - } - CHECK(!cuts_group.empty()); - TEST_AND_RETURN_FALSE(AssignBlockForAdjoiningCuts( - graph, new_part, blob_file, op_indexes, reverse_op_indexes, cuts_group)); - return true; -} - -bool InplaceGenerator::NoTempBlocksRemain(const Graph& graph) { - size_t idx = 0; - for (Graph::const_iterator it = graph.begin(), e = graph.end(); it != e; - ++it, ++idx) { - if (!it->valid) - continue; - const InstallOperation& op = it->aop.op; - if (TempBlocksExistInExtents(op.dst_extents()) || - TempBlocksExistInExtents(op.src_extents())) { - LOG(INFO) << "bad extents in node " << idx; - LOG(INFO) << "so yeah"; - return false; - } - - // Check out-edges: - for (const auto& edge_prop_pair : it->out_edges) { - if (TempBlocksExistInExtents(edge_prop_pair.second.extents) || - TempBlocksExistInExtents(edge_prop_pair.second.write_extents)) { - LOG(INFO) << "bad out edge in node " << idx; - LOG(INFO) << "so yeah"; - return false; - } - } - } - return true; -} - -bool InplaceGenerator::ConvertCutToFullOp(Graph* graph, - const CutEdgeVertexes& cut, - const string& new_part, - BlobFileWriter* blob_file) { - // Drop all incoming edges, keep all outgoing edges - - // Keep all outgoing edges - if ((*graph)[cut.old_dst].aop.op.type() != InstallOperation::REPLACE_BZ && - (*graph)[cut.old_dst].aop.op.type() != InstallOperation::REPLACE) { - Vertex::EdgeMap out_edges = (*graph)[cut.old_dst].out_edges; - graph_utils::DropWriteBeforeDeps(&out_edges); - - // Replace the operation with a REPLACE or REPLACE_BZ to generate the same - // |new_extents| list of blocks and update the graph. - vector<AnnotatedOperation> new_aop; - vector<Extent> new_extents; - ExtentsToVector((*graph)[cut.old_dst].aop.op.dst_extents(), &new_extents); - TEST_AND_RETURN_FALSE(diff_utils::DeltaReadFile( - &new_aop, - "", // old_part - new_part, - vector<Extent>(), // old_extents - new_extents, - {}, // old_deflates - {}, // new_deflates - (*graph)[cut.old_dst].aop.name, - -1, // chunk_blocks, forces to have a single operation. - kInPlacePayloadVersion, - blob_file)); - TEST_AND_RETURN_FALSE(new_aop.size() == 1); - TEST_AND_RETURN_FALSE(AddInstallOpToGraph( - graph, cut.old_dst, nullptr, new_aop.front().op, new_aop.front().name)); - - (*graph)[cut.old_dst].out_edges = out_edges; - - // Right now we don't have doubly-linked edges, so we have to scan - // the whole graph. - graph_utils::DropIncomingEdgesTo(graph, cut.old_dst); - } - - // Delete temp node - (*graph)[cut.old_src].out_edges.erase(cut.new_vertex); - CHECK((*graph)[cut.old_dst].out_edges.find(cut.new_vertex) == - (*graph)[cut.old_dst].out_edges.end()); - (*graph)[cut.new_vertex].valid = false; - LOG(INFO) << "marked node invalid: " << cut.new_vertex; - return true; -} - -bool InplaceGenerator::ConvertGraphToDag(Graph* graph, - const string& new_part, - BlobFileWriter* blob_file, - vector<Vertex::Index>* final_order, - Vertex::Index scratch_vertex) { - CycleBreaker cycle_breaker; - LOG(INFO) << "Finding cycles..."; - set<Edge> cut_edges; - cycle_breaker.BreakCycles(*graph, &cut_edges); - LOG(INFO) << "done finding cycles"; - CheckGraph(*graph); - - // Calculate number of scratch blocks needed - - LOG(INFO) << "Cutting cycles..."; - vector<CutEdgeVertexes> cuts; - TEST_AND_RETURN_FALSE(CutEdges(graph, cut_edges, &cuts)); - LOG(INFO) << "done cutting cycles"; - LOG(INFO) << "There are " << cuts.size() << " cuts."; - CheckGraph(*graph); - - LOG(INFO) << "Creating initial topological order..."; - TopologicalSort(*graph, final_order); - LOG(INFO) << "done with initial topo order"; - CheckGraph(*graph); - - LOG(INFO) << "Moving full ops to the back"; - MoveAndSortFullOpsToBack(graph, final_order); - LOG(INFO) << "done moving full ops to back"; - - vector<vector<Vertex::Index>::size_type> inverse_final_order; - GenerateReverseTopoOrderMap(*final_order, &inverse_final_order); - - SortCutsByTopoOrder(*final_order, &cuts); - - if (!cuts.empty()) - TEST_AND_RETURN_FALSE(AssignTempBlocks( - graph, new_part, blob_file, final_order, &inverse_final_order, cuts)); - LOG(INFO) << "Making sure all temp blocks have been allocated"; - - // Remove the scratch node, if any - if (scratch_vertex != Vertex::kInvalidIndex) { - final_order->erase(final_order->begin() + - inverse_final_order[scratch_vertex]); - (*graph)[scratch_vertex].valid = false; - GenerateReverseTopoOrderMap(*final_order, &inverse_final_order); - } - - graph_utils::DumpGraph(*graph); - CHECK(NoTempBlocksRemain(*graph)); - LOG(INFO) << "done making sure all temp blocks are allocated"; - return true; -} - -void InplaceGenerator::CreateScratchNode(uint64_t start_block, - uint64_t num_blocks, - Vertex* vertex) { - vertex->aop.name = "<scratch>"; - vertex->aop.op.set_type(InstallOperation::REPLACE_BZ); - vertex->aop.op.set_data_offset(0); - vertex->aop.op.set_data_length(0); - Extent* extent = vertex->aop.op.add_dst_extents(); - extent->set_start_block(start_block); - extent->set_num_blocks(num_blocks); -} - -bool InplaceGenerator::AddInstallOpToBlocksVector( - const InstallOperation& operation, - const Graph& graph, - Vertex::Index vertex, - vector<Block>* blocks) { - // See if this is already present. - TEST_AND_RETURN_FALSE(operation.dst_extents_size() > 0); - - enum BlockField { READER = 0, WRITER, BLOCK_FIELD_COUNT }; - for (int field = READER; field < BLOCK_FIELD_COUNT; field++) { - const char* past_participle = (field == READER) ? "read" : "written"; - const google::protobuf::RepeatedPtrField<Extent>& extents = - (field == READER) ? operation.src_extents() : operation.dst_extents(); - Vertex::Index Block::*access_type = - (field == READER) ? &Block::reader : &Block::writer; - - for (const Extent& extent : extents) { - for (uint64_t block = extent.start_block(); - block < (extent.start_block() + extent.num_blocks()); - block++) { - if ((*blocks)[block].*access_type != Vertex::kInvalidIndex) { - LOG(FATAL) << "Block " << block << " is already " << past_participle - << " by " << (*blocks)[block].*access_type << "(" - << graph[(*blocks)[block].*access_type].aop.name - << ") and also " << vertex << "(" << graph[vertex].aop.name - << ")"; - } - (*blocks)[block].*access_type = vertex; - } - } - } - return true; -} - -bool InplaceGenerator::AddInstallOpToGraph(Graph* graph, - Vertex::Index existing_vertex, - vector<Block>* blocks, - const InstallOperation& operation, - const string& op_name) { - Vertex::Index vertex = existing_vertex; - if (vertex == Vertex::kInvalidIndex) { - graph->emplace_back(); - vertex = graph->size() - 1; - } - (*graph)[vertex].aop.op = operation; - CHECK((*graph)[vertex].aop.op.has_type()); - (*graph)[vertex].aop.name = op_name; - - if (blocks) - TEST_AND_RETURN_FALSE(InplaceGenerator::AddInstallOpToBlocksVector( - (*graph)[vertex].aop.op, *graph, vertex, blocks)); - return true; -} - -void InplaceGenerator::ApplyMap(vector<uint64_t>* collection, - const map<uint64_t, uint64_t>& the_map) { - for (uint64_t& elem : *collection) { - const auto& map_it = the_map.find(elem); - if (map_it != the_map.end()) - elem = map_it->second; - } -} - -bool InplaceGenerator::ResolveReadAfterWriteDependencies( - const PartitionConfig& old_part, - const PartitionConfig& new_part, - uint64_t partition_size, - size_t block_size, - BlobFileWriter* blob_file, - vector<AnnotatedOperation>* aops) { - // Convert the operations to the graph. - Graph graph; - CheckGraph(graph); - vector<Block> blocks(std::max(old_part.size, new_part.size) / block_size); - for (const auto& aop : *aops) { - AddInstallOpToGraph( - &graph, Vertex::kInvalidIndex, &blocks, aop.op, aop.name); - } - CheckGraph(graph); - - // Final scratch block (if there's space) - Vertex::Index scratch_vertex = Vertex::kInvalidIndex; - if (blocks.size() < (partition_size / block_size)) { - scratch_vertex = graph.size(); - graph.emplace_back(); - size_t scratch_blocks = (partition_size / block_size) - blocks.size(); - LOG(INFO) << "Added " << scratch_blocks << " scratch space blocks."; - CreateScratchNode(blocks.size(), scratch_blocks, &graph.back()); - } - CheckGraph(graph); - - LOG(INFO) << "Creating edges..."; - CreateEdges(&graph, blocks); - LOG(INFO) << "Done creating edges"; - CheckGraph(graph); - - vector<Vertex::Index> final_order; - TEST_AND_RETURN_FALSE(ConvertGraphToDag( - &graph, new_part.path, blob_file, &final_order, scratch_vertex)); - - // Copy operations over to the |aops| vector in the final_order generated by - // the topological sort. - aops->clear(); - aops->reserve(final_order.size()); - for (const Vertex::Index vertex_index : final_order) { - const Vertex& vertex = graph[vertex_index]; - aops->push_back(vertex.aop); - } - return true; -} - -bool InplaceGenerator::GenerateOperations(const PayloadGenerationConfig& config, - const PartitionConfig& old_part, - const PartitionConfig& new_part, - BlobFileWriter* blob_file, - vector<AnnotatedOperation>* aops) { - TEST_AND_RETURN_FALSE(old_part.name == new_part.name); - TEST_AND_RETURN_FALSE(config.version.major == kInPlacePayloadVersion.major); - TEST_AND_RETURN_FALSE(config.version.minor == kInPlacePayloadVersion.minor); - - ssize_t hard_chunk_blocks = - (config.hard_chunk_size == -1 - ? -1 - : config.hard_chunk_size / config.block_size); - size_t soft_chunk_blocks = config.soft_chunk_size / config.block_size; - uint64_t partition_size = new_part.size; - if (new_part.name == kPartitionNameRoot) - partition_size = config.rootfs_partition_size; - - LOG(INFO) << "Delta compressing " << new_part.name << " partition..."; - TEST_AND_RETURN_FALSE(diff_utils::DeltaReadPartition(aops, - old_part, - new_part, - hard_chunk_blocks, - soft_chunk_blocks, - config.version, - blob_file)); - LOG(INFO) << "Done reading " << new_part.name; - - TEST_AND_RETURN_FALSE(ResolveReadAfterWriteDependencies( - old_part, new_part, partition_size, config.block_size, blob_file, aops)); - LOG(INFO) << "Done reordering " << new_part.name; - return true; -} - -}; // namespace chromeos_update_engine diff --git a/payload_generator/inplace_generator.h b/payload_generator/inplace_generator.h deleted file mode 100644 index e7298d21..00000000 --- a/payload_generator/inplace_generator.h +++ /dev/null @@ -1,240 +0,0 @@ -// -// Copyright (C) 2015 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_INPLACE_GENERATOR_H_ -#define UPDATE_ENGINE_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_ - -#include <map> -#include <set> -#include <string> -#include <vector> - -#include "update_engine/payload_generator/blob_file_writer.h" -#include "update_engine/payload_generator/delta_diff_generator.h" -#include "update_engine/payload_generator/graph_types.h" -#include "update_engine/payload_generator/operations_generator.h" - -// InplaceGenerator contains all functionality related to the inplace algorithm -// for generating update payloads. These are the functions used when delta minor -// version is 1. - -namespace chromeos_update_engine { - -// This struct stores all relevant info for an edge that is cut between -// nodes old_src -> old_dst by creating new vertex new_vertex. The new -// relationship is: -// old_src -(read before)-> new_vertex <-(write before)- old_dst -// new_vertex is a MOVE operation that moves some existing blocks into -// temp space. The temp extents are, by necessity, stored in new_vertex -// (as dst extents) and old_dst (as src extents), but they are also broken -// out into tmp_extents, as the nodes themselves may contain many more -// extents. -struct CutEdgeVertexes { - Vertex::Index new_vertex; - Vertex::Index old_src; - Vertex::Index old_dst; - std::vector<Extent> tmp_extents; -}; - -class InplaceGenerator : public OperationsGenerator { - public: - // Represents a disk block on the install partition. - struct Block { - // During install, each block on the install partition will be written - // and some may be read (in all likelihood, many will be read). - // The reading and writing will be performed by InstallOperations, - // each of which has a corresponding vertex in a graph. - // A Block object tells which vertex will read or write this block - // at install time. - // Generally, there will be a vector of Block objects whose length - // is the number of blocks on the install partition. - Block() : reader(Vertex::kInvalidIndex), writer(Vertex::kInvalidIndex) {} - Vertex::Index reader; - Vertex::Index writer; - }; - - InplaceGenerator() = default; - - // Checks all the operations in the graph have a type assigned. - static void CheckGraph(const Graph& graph); - - // Modifies blocks read by 'op' so that any blocks referred to by - // 'remove_extents' are replaced with blocks from 'replace_extents'. - // 'remove_extents' and 'replace_extents' must be the same number of blocks. - // Blocks will be substituted in the order listed in the vectors. - // E.g. if 'op' reads blocks 1, 2, 3, 4, 5, 6, 7, 8, remove_extents - // contains blocks 6, 2, 3, 5, and replace blocks contains - // 12, 13, 14, 15, then op will be changed to read from: - // 1, 13, 14, 4, 15, 12, 7, 8 - static void SubstituteBlocks(Vertex* vertex, - const std::vector<Extent>& remove_extents, - const std::vector<Extent>& replace_extents); - - // Cuts 'edges' from 'graph' according to the AU algorithm. This means - // for each edge A->B, remove the dependency that B occur before A. - // Do this by creating a new operation X that copies from the blocks - // specified by the edge's properties to temp space T. Modify B to read - // from T rather than the blocks in the edge. Modify A to depend on X, - // but not on B. Free space is found by looking in 'blocks'. - // Returns true on success. - static bool CutEdges(Graph* graph, - const std::set<Edge>& edges, - std::vector<CutEdgeVertexes>* out_cuts); - - // Creates all the edges for the graph. Writers of a block point to - // readers of the same block. This is because for an edge A->B, B - // must complete before A executes. - static void CreateEdges(Graph* graph, const std::vector<Block>& blocks); - - // Takes |op_indexes|, which is effectively a mapping from order in - // which the op is performed -> graph vertex index, and produces the - // reverse: a mapping from graph vertex index -> op_indexes index. - static void GenerateReverseTopoOrderMap( - const std::vector<Vertex::Index>& op_indexes, - std::vector<std::vector<Vertex::Index>::size_type>* reverse_op_indexes); - - // Sorts the vector |cuts| by its |cuts[].old_dest| member. Order is - // determined by the order of elements in op_indexes. - static void SortCutsByTopoOrder(const std::vector<Vertex::Index>& op_indexes, - std::vector<CutEdgeVertexes>* cuts); - - // Given a topologically sorted graph |op_indexes| and |graph|, alters - // |op_indexes| to move all the full operations to the end of the vector. - // Full operations should not be depended on, so this is safe. - static void MoveAndSortFullOpsToBack(Graph* graph, - std::vector<Vertex::Index>* op_indexes); - - // Returns true iff there are no extents in the graph that refer to temp - // blocks. Temp blocks are in the range [kTempBlockStart, kSparseHole). - static bool NoTempBlocksRemain(const Graph& graph); - - // Takes a |graph|, which has edges that must be cut, as listed in - // |cuts|. Cuts the edges. Maintains a list in which the operations - // will be performed (in |op_indexes|) and the reverse (in - // |reverse_op_indexes|). Cutting edges requires scratch space, and - // if insufficient scratch is found, the file is reread and will be - // send down (either as REPLACE or REPLACE_BZ). Returns true on - // success. - static bool AssignTempBlocks( - Graph* graph, - const std::string& new_part, - BlobFileWriter* blob_file, - std::vector<Vertex::Index>* op_indexes, - std::vector<std::vector<Vertex::Index>::size_type>* reverse_op_indexes, - const std::vector<CutEdgeVertexes>& cuts); - - // Handles allocation of temp blocks to a cut edge by converting the - // dest node to a full op. This removes the need for temp blocks, but - // comes at the cost of a worse compression ratio. - // For example, say we have A->B->A. It would first be cut to form: - // A->B->N<-A, where N copies blocks to temp space. If there are no - // temp blocks, this function can be called to convert it to the form: - // A->B. Now, A is a full operation. - static bool ConvertCutToFullOp(Graph* graph, - const CutEdgeVertexes& cut, - const std::string& new_part, - BlobFileWriter* blob_file); - - // Takes a graph, which is not a DAG, which represents the files just - // read from disk, and converts it into a DAG by breaking all cycles - // and finding temp space to resolve broken edges. - // The final order of the nodes is given in |final_order| - // Some files may need to be reread from disk, thus |fd| and - // |data_file_size| are be passed. - // If |scratch_vertex| is not kInvalidIndex, removes it from - // |final_order| before returning. - // Returns true on success. - static bool ConvertGraphToDag(Graph* graph, - const std::string& new_part, - BlobFileWriter* blob_file, - std::vector<Vertex::Index>* final_order, - Vertex::Index scratch_vertex); - - // Creates a dummy REPLACE_BZ node in the given |vertex|. This can be used - // to provide scratch space. The node writes |num_blocks| blocks starting at - // |start_block|The node should be marked invalid before writing all nodes to - // the output file. - static void CreateScratchNode(uint64_t start_block, - uint64_t num_blocks, - Vertex* vertex); - - // The |blocks| vector contains a reader and writer for each block on the - // filesystem that's being in-place updated. We populate the reader/writer - // fields of |blocks| by calling this function. - // For each block in |operation| that is read or written, find that block - // in |blocks| and set the reader/writer field to the vertex passed. - // |graph| is not strictly necessary, but useful for printing out - // error messages. - static bool AddInstallOpToBlocksVector(const InstallOperation& operation, - const Graph& graph, - Vertex::Index vertex, - std::vector<Block>* blocks); - - // Add a vertex (if |existing_vertex| is kInvalidVertex) or update an - // |existing_vertex| with the passed |operation|. - // This method will also register the vertex as the reader or writer of the - // blocks involved in the operation updating the |blocks| vector. The - // |op_name| associated with the Vertex is used for logging purposes. - static bool AddInstallOpToGraph(Graph* graph, - Vertex::Index existing_vertex, - std::vector<Block>* blocks, - const InstallOperation& operation, - const std::string& op_name); - - // Apply the transformation stored in |the_map| to the |collection| vector - // replacing the map keys found in |collection| with its associated value in - // |the_map|. - static void ApplyMap(std::vector<uint64_t>* collection, - const std::map<uint64_t, uint64_t>& the_map); - - // Resolve all read-after-write dependencies in the operation list |aops|. The - // operations in |aops| are such that they generate the desired |new_part| if - // applied reading always from the original image. This function reorders the - // operations and generates new operations when needed to make these - // operations produce the same |new_part| result when applied in-place. - // The new operations will create blobs in |data_file_fd| and update - // the file size pointed by |data_file_size| if needed. - // On success, stores the new operations in |aops| in the right order and - // returns true. - static bool ResolveReadAfterWriteDependencies( - const PartitionConfig& old_part, - const PartitionConfig& new_part, - uint64_t partition_size, - size_t block_size, - BlobFileWriter* blob_file, - std::vector<AnnotatedOperation>* aops); - - // Generate the update payload operations for the given partition using - // only operations that read from the target and/or write to the target, - // hence, applying the payload "in-place" in the target partition. This method - // assumes that the contents of the source image are pre-copied to the target - // partition, up to the size of the source image. Use this method to generate - // a delta update with the minor version kInPlaceMinorPayloadVersion. - // The operations are stored in |aops|. All the offsets in the operations - // reference the data written to |blob_file|. - bool GenerateOperations(const PayloadGenerationConfig& config, - const PartitionConfig& old_part, - const PartitionConfig& new_part, - BlobFileWriter* blob_file, - std::vector<AnnotatedOperation>* aops) override; - - private: - DISALLOW_COPY_AND_ASSIGN(InplaceGenerator); -}; - -}; // namespace chromeos_update_engine - -#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_ diff --git a/payload_generator/inplace_generator_unittest.cc b/payload_generator/inplace_generator_unittest.cc deleted file mode 100644 index 8028f366..00000000 --- a/payload_generator/inplace_generator_unittest.cc +++ /dev/null @@ -1,752 +0,0 @@ -// -// Copyright (C) 2015 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/inplace_generator.h" - -#include <map> -#include <memory> -#include <set> -#include <sstream> -#include <string> -#include <utility> -#include <vector> - -#include <base/format_macros.h> -#include <base/logging.h> -#include <base/strings/string_util.h> -#include <base/strings/stringprintf.h> -#include <gtest/gtest.h> - -#include "update_engine/common/test_utils.h" -#include "update_engine/common/utils.h" -#include "update_engine/payload_generator/cycle_breaker.h" -#include "update_engine/payload_generator/delta_diff_generator.h" -#include "update_engine/payload_generator/delta_diff_utils.h" -#include "update_engine/payload_generator/extent_ranges.h" -#include "update_engine/payload_generator/graph_types.h" -#include "update_engine/payload_generator/graph_utils.h" - -using std::map; -using std::set; -using std::string; -using std::stringstream; -using std::vector; - -namespace chromeos_update_engine { - -using Block = InplaceGenerator::Block; - -namespace { - -void GenVertex(Vertex* out, - const vector<Extent>& src_extents, - const vector<Extent>& dst_extents, - const string& path, - InstallOperation::Type type) { - out->aop.op.set_type(type); - out->aop.name = path; - StoreExtents(src_extents, out->aop.op.mutable_src_extents()); - StoreExtents(dst_extents, out->aop.op.mutable_dst_extents()); -} - -vector<Extent> VectOfExt(uint64_t start_block, uint64_t num_blocks) { - return vector<Extent>(1, ExtentForRange(start_block, num_blocks)); -} - -EdgeProperties EdgeWithReadDep(const vector<Extent>& extents) { - EdgeProperties ret; - ret.extents = extents; - return ret; -} - -EdgeProperties EdgeWithWriteDep(const vector<Extent>& extents) { - EdgeProperties ret; - ret.write_extents = extents; - return ret; -} - -template <typename T> -void DumpVect(const vector<T>& vect) { - stringstream ss(stringstream::out); - for (typename vector<T>::const_iterator it = vect.begin(), e = vect.end(); - it != e; - ++it) { - ss << *it << ", "; - } - LOG(INFO) << "{" << ss.str() << "}"; -} - -void AppendExtent(vector<Extent>* vect, uint64_t start, uint64_t length) { - vect->resize(vect->size() + 1); - vect->back().set_start_block(start); - vect->back().set_num_blocks(length); -} - -void OpAppendExtent(InstallOperation* op, uint64_t start, uint64_t length) { - Extent* extent = op->add_src_extents(); - extent->set_start_block(start); - extent->set_num_blocks(length); -} - -} // namespace - -class InplaceGeneratorTest : public ::testing::Test { - protected: - // Initialize |blob_path_|, |blob_file_size_| and |blob_file_fd_| variables - // with a new blob file. The file is closed and removed automatically when - // the test finishes. - void CreateBlobFile() { - // blob_fd_closer_ takes a pointer to blob_fd_. Make sure we destroy a - // previous instance before overriding blob_fd_. - blob_fd_closer_.reset(); - EXPECT_TRUE(utils::MakeTempFile( - "InplaceGenerator_blob_file.XXXXXX", &blob_path_, &blob_fd_)); - blob_path_unlinker_.reset(new ScopedPathUnlinker(blob_path_)); - blob_fd_closer_.reset(new ScopedFdCloser(&blob_fd_)); - blob_file_size_ = 0; - EXPECT_GE(blob_fd_, 0); - blob_file_.reset(new BlobFileWriter(blob_fd_, &blob_file_size_)); - } - - // Dump the list of operations |aops| in case of test failure. - void DumpAopsOnFailure(const vector<AnnotatedOperation>& aops) { - if (HasNonfatalFailure()) { - LOG(INFO) << "Result operation list:"; - for (const auto& aop : aops) { - LOG(INFO) << aop; - } - } - } - - // Blob file name, file descriptor and file size used to store operation - // blobs. - string blob_path_; - int blob_fd_{-1}; - off_t blob_file_size_{0}; - std::unique_ptr<BlobFileWriter> blob_file_; - std::unique_ptr<ScopedPathUnlinker> blob_path_unlinker_; - std::unique_ptr<ScopedFdCloser> blob_fd_closer_; -}; - -TEST_F(InplaceGeneratorTest, BlockDefaultValues) { - // Tests that a Block is initialized with the default values as a - // Vertex::kInvalidIndex. This is required by the delta generators. - Block block; - EXPECT_EQ(Vertex::kInvalidIndex, block.reader); - EXPECT_EQ(Vertex::kInvalidIndex, block.writer); -} - -TEST_F(InplaceGeneratorTest, SubstituteBlocksTest) { - vector<Extent> remove_blocks; - AppendExtent(&remove_blocks, 3, 3); - AppendExtent(&remove_blocks, 7, 1); - vector<Extent> replace_blocks; - AppendExtent(&replace_blocks, 10, 2); - AppendExtent(&replace_blocks, 13, 2); - Vertex vertex; - InstallOperation& op = vertex.aop.op; - OpAppendExtent(&op, 4, 3); - OpAppendExtent(&op, kSparseHole, 4); // Sparse hole in file - OpAppendExtent(&op, 3, 1); - OpAppendExtent(&op, 7, 3); - - InplaceGenerator::SubstituteBlocks(&vertex, remove_blocks, replace_blocks); - - EXPECT_EQ(7, op.src_extents_size()); - EXPECT_EQ(11U, op.src_extents(0).start_block()); - EXPECT_EQ(1U, op.src_extents(0).num_blocks()); - EXPECT_EQ(13U, op.src_extents(1).start_block()); - EXPECT_EQ(1U, op.src_extents(1).num_blocks()); - EXPECT_EQ(6U, op.src_extents(2).start_block()); - EXPECT_EQ(1U, op.src_extents(2).num_blocks()); - EXPECT_EQ(kSparseHole, op.src_extents(3).start_block()); - EXPECT_EQ(4U, op.src_extents(3).num_blocks()); - EXPECT_EQ(10U, op.src_extents(4).start_block()); - EXPECT_EQ(1U, op.src_extents(4).num_blocks()); - EXPECT_EQ(14U, op.src_extents(5).start_block()); - EXPECT_EQ(1U, op.src_extents(5).num_blocks()); - EXPECT_EQ(8U, op.src_extents(6).start_block()); - EXPECT_EQ(2U, op.src_extents(6).num_blocks()); -} - -TEST_F(InplaceGeneratorTest, CutEdgesTest) { - Graph graph; - vector<Block> blocks(9); - - // Create nodes in graph - { - graph.resize(graph.size() + 1); - graph.back().aop.op.set_type(InstallOperation::MOVE); - // Reads from blocks 3, 5, 7 - vector<Extent> extents; - AppendBlockToExtents(&extents, 3); - AppendBlockToExtents(&extents, 5); - AppendBlockToExtents(&extents, 7); - StoreExtents(extents, graph.back().aop.op.mutable_src_extents()); - blocks[3].reader = graph.size() - 1; - blocks[5].reader = graph.size() - 1; - blocks[7].reader = graph.size() - 1; - - // Writes to blocks 1, 2, 4 - extents.clear(); - AppendBlockToExtents(&extents, 1); - AppendBlockToExtents(&extents, 2); - AppendBlockToExtents(&extents, 4); - StoreExtents(extents, graph.back().aop.op.mutable_dst_extents()); - blocks[1].writer = graph.size() - 1; - blocks[2].writer = graph.size() - 1; - blocks[4].writer = graph.size() - 1; - } - { - graph.resize(graph.size() + 1); - graph.back().aop.op.set_type(InstallOperation::MOVE); - // Reads from blocks 1, 2, 4 - vector<Extent> extents; - AppendBlockToExtents(&extents, 1); - AppendBlockToExtents(&extents, 2); - AppendBlockToExtents(&extents, 4); - StoreExtents(extents, graph.back().aop.op.mutable_src_extents()); - blocks[1].reader = graph.size() - 1; - blocks[2].reader = graph.size() - 1; - blocks[4].reader = graph.size() - 1; - - // Writes to blocks 3, 5, 6 - extents.clear(); - AppendBlockToExtents(&extents, 3); - AppendBlockToExtents(&extents, 5); - AppendBlockToExtents(&extents, 6); - StoreExtents(extents, graph.back().aop.op.mutable_dst_extents()); - blocks[3].writer = graph.size() - 1; - blocks[5].writer = graph.size() - 1; - blocks[6].writer = graph.size() - 1; - } - - // Create edges - InplaceGenerator::CreateEdges(&graph, blocks); - - // Find cycles - CycleBreaker cycle_breaker; - set<Edge> cut_edges; - cycle_breaker.BreakCycles(graph, &cut_edges); - - EXPECT_EQ(1U, cut_edges.size()); - EXPECT_TRUE(cut_edges.end() != - cut_edges.find(std::pair<Vertex::Index, Vertex::Index>(1, 0))); - - vector<CutEdgeVertexes> cuts; - EXPECT_TRUE(InplaceGenerator::CutEdges(&graph, cut_edges, &cuts)); - - EXPECT_EQ(3U, graph.size()); - - // Check new node in graph: - EXPECT_EQ(InstallOperation::MOVE, graph.back().aop.op.type()); - EXPECT_EQ(2, graph.back().aop.op.src_extents_size()); - EXPECT_EQ(1, graph.back().aop.op.dst_extents_size()); - EXPECT_EQ(kTempBlockStart, graph.back().aop.op.dst_extents(0).start_block()); - EXPECT_EQ(2U, graph.back().aop.op.dst_extents(0).num_blocks()); - EXPECT_TRUE(graph.back().out_edges.empty()); - - // Check that old node reads from new blocks - EXPECT_EQ(2, graph[0].aop.op.src_extents_size()); - EXPECT_EQ(kTempBlockStart, graph[0].aop.op.src_extents(0).start_block()); - EXPECT_EQ(2U, graph[0].aop.op.src_extents(0).num_blocks()); - EXPECT_EQ(7U, graph[0].aop.op.src_extents(1).start_block()); - EXPECT_EQ(1U, graph[0].aop.op.src_extents(1).num_blocks()); - - // And that the old dst extents haven't changed - EXPECT_EQ(2, graph[0].aop.op.dst_extents_size()); - EXPECT_EQ(1U, graph[0].aop.op.dst_extents(0).start_block()); - EXPECT_EQ(2U, graph[0].aop.op.dst_extents(0).num_blocks()); - EXPECT_EQ(4U, graph[0].aop.op.dst_extents(1).start_block()); - EXPECT_EQ(1U, graph[0].aop.op.dst_extents(1).num_blocks()); - - // Ensure it only depends on the next node and the new temp node - EXPECT_EQ(2U, graph[0].out_edges.size()); - EXPECT_TRUE(graph[0].out_edges.end() != graph[0].out_edges.find(1)); - EXPECT_TRUE(graph[0].out_edges.end() != - graph[0].out_edges.find(graph.size() - 1)); - - // Check second node has unchanged extents - EXPECT_EQ(2, graph[1].aop.op.src_extents_size()); - EXPECT_EQ(1U, graph[1].aop.op.src_extents(0).start_block()); - EXPECT_EQ(2U, graph[1].aop.op.src_extents(0).num_blocks()); - EXPECT_EQ(4U, graph[1].aop.op.src_extents(1).start_block()); - EXPECT_EQ(1U, graph[1].aop.op.src_extents(1).num_blocks()); - - EXPECT_EQ(2, graph[1].aop.op.dst_extents_size()); - EXPECT_EQ(3U, graph[1].aop.op.dst_extents(0).start_block()); - EXPECT_EQ(1U, graph[1].aop.op.dst_extents(0).num_blocks()); - EXPECT_EQ(5U, graph[1].aop.op.dst_extents(1).start_block()); - EXPECT_EQ(2U, graph[1].aop.op.dst_extents(1).num_blocks()); - - // Ensure it only depends on the next node - EXPECT_EQ(1U, graph[1].out_edges.size()); - EXPECT_TRUE(graph[1].out_edges.end() != graph[1].out_edges.find(2)); -} - -TEST_F(InplaceGeneratorTest, AssignTempBlocksReuseTest) { - Graph graph(9); - - const vector<Extent> empt; - uint64_t tmp = kTempBlockStart; - const string kFilename = "/foo"; - - vector<CutEdgeVertexes> cuts; - cuts.resize(3); - - // Simple broken loop: - GenVertex( - &graph[0], VectOfExt(0, 1), VectOfExt(1, 1), "", InstallOperation::MOVE); - GenVertex(&graph[1], - VectOfExt(tmp, 1), - VectOfExt(0, 1), - "", - InstallOperation::MOVE); - GenVertex(&graph[2], - VectOfExt(1, 1), - VectOfExt(tmp, 1), - "", - InstallOperation::MOVE); - // Corresponding edges: - graph[0].out_edges[2] = EdgeWithReadDep(VectOfExt(1, 1)); - graph[1].out_edges[2] = EdgeWithWriteDep(VectOfExt(tmp, 1)); - graph[1].out_edges[0] = EdgeWithReadDep(VectOfExt(0, 1)); - // Store the cut: - cuts[0].old_dst = 1; - cuts[0].old_src = 0; - cuts[0].new_vertex = 2; - cuts[0].tmp_extents = VectOfExt(tmp, 1); - tmp++; - - // Slightly more complex pair of loops: - GenVertex( - &graph[3], VectOfExt(4, 2), VectOfExt(2, 2), "", InstallOperation::MOVE); - GenVertex( - &graph[4], VectOfExt(6, 1), VectOfExt(7, 1), "", InstallOperation::MOVE); - GenVertex(&graph[5], - VectOfExt(tmp, 3), - VectOfExt(4, 3), - kFilename, - InstallOperation::MOVE); - GenVertex(&graph[6], - VectOfExt(2, 2), - VectOfExt(tmp, 2), - "", - InstallOperation::MOVE); - GenVertex(&graph[7], - VectOfExt(7, 1), - VectOfExt(tmp + 2, 1), - "", - InstallOperation::MOVE); - // Corresponding edges: - graph[3].out_edges[6] = EdgeWithReadDep(VectOfExt(2, 2)); - graph[4].out_edges[7] = EdgeWithReadDep(VectOfExt(7, 1)); - graph[5].out_edges[6] = EdgeWithWriteDep(VectOfExt(tmp, 2)); - graph[5].out_edges[7] = EdgeWithWriteDep(VectOfExt(tmp + 2, 1)); - graph[5].out_edges[3] = EdgeWithReadDep(VectOfExt(4, 2)); - graph[5].out_edges[4] = EdgeWithReadDep(VectOfExt(6, 1)); - // Store the cuts: - cuts[1].old_dst = 5; - cuts[1].old_src = 3; - cuts[1].new_vertex = 6; - cuts[1].tmp_extents = VectOfExt(tmp, 2); - cuts[2].old_dst = 5; - cuts[2].old_src = 4; - cuts[2].new_vertex = 7; - cuts[2].tmp_extents = VectOfExt(tmp + 2, 1); - - // Supplier of temp block: - GenVertex(&graph[8], empt, VectOfExt(8, 1), "", InstallOperation::REPLACE); - - // Specify the final order: - vector<Vertex::Index> op_indexes; - op_indexes.push_back(2); - op_indexes.push_back(0); - op_indexes.push_back(1); - op_indexes.push_back(6); - op_indexes.push_back(3); - op_indexes.push_back(7); - op_indexes.push_back(4); - op_indexes.push_back(5); - op_indexes.push_back(8); - - vector<vector<Vertex::Index>::size_type> reverse_op_indexes; - InplaceGenerator::GenerateReverseTopoOrderMap(op_indexes, - &reverse_op_indexes); - - CreateBlobFile(); - EXPECT_TRUE(InplaceGenerator::AssignTempBlocks(&graph, - "/dev/zero", - blob_file_.get(), - &op_indexes, - &reverse_op_indexes, - cuts)); - EXPECT_FALSE(graph[6].valid); - EXPECT_FALSE(graph[7].valid); - EXPECT_EQ(1, graph[1].aop.op.src_extents_size()); - EXPECT_EQ(2U, graph[1].aop.op.src_extents(0).start_block()); - EXPECT_EQ(1U, graph[1].aop.op.src_extents(0).num_blocks()); - EXPECT_EQ(InstallOperation::REPLACE_BZ, graph[5].aop.op.type()); -} - -TEST_F(InplaceGeneratorTest, MoveAndSortFullOpsToBackTest) { - Graph graph(4); - graph[0].aop.name = "A"; - graph[0].aop.op.set_type(InstallOperation::REPLACE); - graph[1].aop.name = "B"; - graph[1].aop.op.set_type(InstallOperation::BSDIFF); - graph[2].aop.name = "C"; - graph[2].aop.op.set_type(InstallOperation::REPLACE_BZ); - graph[3].aop.name = "D"; - graph[3].aop.op.set_type(InstallOperation::MOVE); - - vector<Vertex::Index> vect(graph.size()); - - for (vector<Vertex::Index>::size_type i = 0; i < vect.size(); ++i) { - vect[i] = i; - } - InplaceGenerator::MoveAndSortFullOpsToBack(&graph, &vect); - EXPECT_EQ(vect.size(), graph.size()); - EXPECT_EQ(graph[vect[0]].aop.name, "B"); - EXPECT_EQ(graph[vect[1]].aop.name, "D"); - EXPECT_EQ(graph[vect[2]].aop.name, "A"); - EXPECT_EQ(graph[vect[3]].aop.name, "C"); -} - -TEST_F(InplaceGeneratorTest, AssignTempBlocksTest) { - Graph graph(9); - const vector<Extent> empt; // empty - const string kFilename = "/foo"; - - // Some scratch space: - GenVertex(&graph[0], empt, VectOfExt(200, 1), "", InstallOperation::REPLACE); - GenVertex(&graph[1], empt, VectOfExt(210, 10), "", InstallOperation::REPLACE); - GenVertex(&graph[2], empt, VectOfExt(220, 1), "", InstallOperation::REPLACE); - - // A cycle that requires 10 blocks to break: - GenVertex(&graph[3], - VectOfExt(10, 11), - VectOfExt(0, 9), - "", - InstallOperation::BSDIFF); - graph[3].out_edges[4] = EdgeWithReadDep(VectOfExt(0, 9)); - GenVertex(&graph[4], - VectOfExt(0, 9), - VectOfExt(10, 11), - "", - InstallOperation::BSDIFF); - graph[4].out_edges[3] = EdgeWithReadDep(VectOfExt(10, 11)); - - // A cycle that requires 9 blocks to break: - GenVertex(&graph[5], - VectOfExt(40, 11), - VectOfExt(30, 10), - "", - InstallOperation::BSDIFF); - graph[5].out_edges[6] = EdgeWithReadDep(VectOfExt(30, 10)); - GenVertex(&graph[6], - VectOfExt(30, 10), - VectOfExt(40, 11), - "", - InstallOperation::BSDIFF); - graph[6].out_edges[5] = EdgeWithReadDep(VectOfExt(40, 11)); - - // A cycle that requires 40 blocks to break (which is too many): - GenVertex(&graph[7], - VectOfExt(120, 50), - VectOfExt(60, 40), - "", - InstallOperation::BSDIFF); - graph[7].out_edges[8] = EdgeWithReadDep(VectOfExt(60, 40)); - GenVertex(&graph[8], - VectOfExt(60, 40), - VectOfExt(120, 50), - kFilename, - InstallOperation::BSDIFF); - graph[8].out_edges[7] = EdgeWithReadDep(VectOfExt(120, 50)); - - graph_utils::DumpGraph(graph); - - vector<Vertex::Index> final_order; - - CreateBlobFile(); - EXPECT_TRUE(InplaceGenerator::ConvertGraphToDag(&graph, - "/dev/zero", - blob_file_.get(), - &final_order, - Vertex::kInvalidIndex)); - - Graph expected_graph(12); - GenVertex(&expected_graph[0], - empt, - VectOfExt(200, 1), - "", - InstallOperation::REPLACE); - GenVertex(&expected_graph[1], - empt, - VectOfExt(210, 10), - "", - InstallOperation::REPLACE); - GenVertex(&expected_graph[2], - empt, - VectOfExt(220, 1), - "", - InstallOperation::REPLACE); - GenVertex(&expected_graph[3], - VectOfExt(10, 11), - VectOfExt(0, 9), - "", - InstallOperation::BSDIFF); - expected_graph[3].out_edges[9] = EdgeWithReadDep(VectOfExt(0, 9)); - GenVertex(&expected_graph[4], - VectOfExt(60, 9), - VectOfExt(10, 11), - "", - InstallOperation::BSDIFF); - expected_graph[4].out_edges[3] = EdgeWithReadDep(VectOfExt(10, 11)); - expected_graph[4].out_edges[9] = EdgeWithWriteDep(VectOfExt(60, 9)); - GenVertex(&expected_graph[5], - VectOfExt(40, 11), - VectOfExt(30, 10), - "", - InstallOperation::BSDIFF); - expected_graph[5].out_edges[10] = EdgeWithReadDep(VectOfExt(30, 10)); - - GenVertex(&expected_graph[6], - VectOfExt(60, 10), - VectOfExt(40, 11), - "", - InstallOperation::BSDIFF); - expected_graph[6].out_edges[5] = EdgeWithReadDep(VectOfExt(40, 11)); - expected_graph[6].out_edges[10] = EdgeWithWriteDep(VectOfExt(60, 10)); - - GenVertex(&expected_graph[7], - VectOfExt(120, 50), - VectOfExt(60, 40), - "", - InstallOperation::BSDIFF); - expected_graph[7].out_edges[6] = EdgeWithReadDep(VectOfExt(60, 10)); - - GenVertex(&expected_graph[8], - empt, - VectOfExt(0, 50), - "/foo", - InstallOperation::REPLACE_BZ); - expected_graph[8].out_edges[7] = EdgeWithReadDep(VectOfExt(120, 50)); - - GenVertex(&expected_graph[9], - VectOfExt(0, 9), - VectOfExt(60, 9), - "", - InstallOperation::MOVE); - - GenVertex(&expected_graph[10], - VectOfExt(30, 10), - VectOfExt(60, 10), - "", - InstallOperation::MOVE); - expected_graph[10].out_edges[4] = EdgeWithReadDep(VectOfExt(60, 9)); - - EXPECT_EQ(12U, graph.size()); - EXPECT_FALSE(graph.back().valid); - for (Graph::size_type i = 0; i < graph.size() - 1; i++) { - EXPECT_TRUE(graph[i].out_edges == expected_graph[i].out_edges); - if (i == 8) { - // special case - } else { - // EXPECT_TRUE(graph[i] == expected_graph[i]) << "i = " << i; - } - } -} - -TEST_F(InplaceGeneratorTest, CreateScratchNodeTest) { - Vertex vertex; - InplaceGenerator::CreateScratchNode(12, 34, &vertex); - EXPECT_EQ(InstallOperation::REPLACE_BZ, vertex.aop.op.type()); - EXPECT_EQ(0U, vertex.aop.op.data_offset()); - EXPECT_EQ(0U, vertex.aop.op.data_length()); - EXPECT_EQ(1, vertex.aop.op.dst_extents_size()); - EXPECT_EQ(12U, vertex.aop.op.dst_extents(0).start_block()); - EXPECT_EQ(34U, vertex.aop.op.dst_extents(0).num_blocks()); -} - -TEST_F(InplaceGeneratorTest, ApplyMapTest) { - vector<uint64_t> collection = {1, 2, 3, 4, 6}; - vector<uint64_t> expected_values = {1, 2, 5, 4, 8}; - map<uint64_t, uint64_t> value_map; - value_map[3] = 5; - value_map[6] = 8; - value_map[5] = 10; - - InplaceGenerator::ApplyMap(&collection, value_map); - EXPECT_EQ(expected_values, collection); -} - -// We can't produce MOVE operations with a source or destination in the block 0. -// This test checks that the cycle breaker procedure doesn't produce such -// operations. -TEST_F(InplaceGeneratorTest, ResolveReadAfterWriteDependenciesAvoidMoveToZero) { - size_t block_size = 4096; - size_t num_blocks = 4; - vector<AnnotatedOperation> aops; - - // Create a REPLACE_BZ for block 0, and a circular dependency among all other - // blocks. This situation would prefer to issue a MOVE to scratch space and - // the only available block is 0. - aops.emplace_back(); - aops.back().name = base::StringPrintf("<bz-block-0>"); - aops.back().op.set_type(InstallOperation::REPLACE_BZ); - StoreExtents({ExtentForRange(0, 1)}, aops.back().op.mutable_dst_extents()); - - for (size_t i = 1; i < num_blocks; i++) { - AnnotatedOperation aop; - aop.name = base::StringPrintf("<op-%" PRIuS ">", i); - aop.op.set_type(InstallOperation::BSDIFF); - StoreExtents({ExtentForRange(1 + i % (num_blocks - 1), 1)}, - aop.op.mutable_src_extents()); - StoreExtents({ExtentForRange(i, 1)}, aop.op.mutable_dst_extents()); - aops.push_back(aop); - } - - PartitionConfig part("part"); - part.path = "/dev/zero"; - part.size = num_blocks * block_size; - - CreateBlobFile(); - - // We ran two tests here. The first one without enough blocks for the scratch - // space, forcing it to create a new full operation and the second case with - // one extra block in the partition that can be used for the move operation. - for (const auto part_blocks : vector<uint64_t>{num_blocks, num_blocks + 1}) { - SCOPED_TRACE( - base::StringPrintf("Using partition_blocks=%" PRIu64, part_blocks)); - vector<AnnotatedOperation> result_aops = aops; - EXPECT_TRUE(InplaceGenerator::ResolveReadAfterWriteDependencies( - part, - part, - part_blocks * block_size, - block_size, - blob_file_.get(), - &result_aops)); - - size_t full_ops = 0; - for (const auto& aop : result_aops) { - if (diff_utils::IsAReplaceOperation(aop.op.type())) - full_ops++; - - if (aop.op.type() != InstallOperation::MOVE) - continue; - for (const Extent& extent : aop.op.src_extents()) { - EXPECT_NE(0U, extent.start_block()) - << "On src extents for aop: " << aop; - } - for (const Extent& extent : aop.op.dst_extents()) { - EXPECT_NE(0U, extent.start_block()) - << "On dst extents for aop: " << aop; - } - } - - // If there's extra space in the partition, it should not use a new full - // operation for it. - EXPECT_EQ(part_blocks == num_blocks ? 2U : 1U, full_ops); - - DumpAopsOnFailure(result_aops); - } -} - -// Test that we can shrink a filesystem and break cycles. -TEST_F(InplaceGeneratorTest, ResolveReadAfterWriteDependenciesShrinkData) { - size_t block_size = 4096; - size_t old_blocks = 10; - size_t new_blocks = 8; - vector<AnnotatedOperation> aops; - - // Create a loop using the blocks 1-6 and one other operation writing to the - // block 7 from outside the new partition. The loop in the blocks 1-6 uses - // two-block operations, so it needs two blocks of scratch space. It can't use - // the block 0 as scratch space (see previous test) and it can't use the - // blocks 7 or 8 due the last move operation. - - aops.emplace_back(); - aops.back().name = base::StringPrintf("<bz-block-0>"); - aops.back().op.set_type(InstallOperation::REPLACE_BZ); - StoreExtents({ExtentForRange(0, 1)}, aops.back().op.mutable_dst_extents()); - - const size_t num_ops = 3; - for (size_t i = 0; i < num_ops; i++) { - AnnotatedOperation aop; - aop.name = base::StringPrintf("<op-%" PRIuS ">", i); - aop.op.set_type(InstallOperation::BSDIFF); - StoreExtents({ExtentForRange(1 + 2 * i, 2)}, aop.op.mutable_src_extents()); - StoreExtents({ExtentForRange(1 + 2 * ((i + 1) % num_ops), 2)}, - aop.op.mutable_dst_extents()); - aops.push_back(aop); - } - - { - AnnotatedOperation aop; - aop.name = "<op-shrink>"; - aop.op.set_type(InstallOperation::BSDIFF); - StoreExtents({ExtentForRange(8, 1)}, aop.op.mutable_src_extents()); - StoreExtents({ExtentForRange(7, 1)}, aop.op.mutable_dst_extents()); - aops.push_back(aop); - } - - PartitionConfig old_part("part"); - old_part.path = "/dev/zero"; - old_part.size = old_blocks * block_size; - - PartitionConfig new_part("part"); - new_part.path = "/dev/zero"; - new_part.size = new_blocks * block_size; - - CreateBlobFile(); - - EXPECT_TRUE(InplaceGenerator::ResolveReadAfterWriteDependencies( - old_part, - new_part, - (old_blocks + 2) * block_size, // enough scratch space. - block_size, - blob_file_.get(), - &aops)); - - size_t full_ops = 0; - for (const auto& aop : aops) { - if (diff_utils::IsAReplaceOperation(aop.op.type())) - full_ops++; - } - // There should be only one REPLACE* operation, the one we added for block 0. - EXPECT_EQ(1U, full_ops); - - // There should be only one MOVE operation, the one used to break the loop - // which should write to scratch space past the block 7 (the last block of the - // new partition) which is being written later. - size_t move_ops = 0; - for (const auto& aop : aops) { - if (aop.op.type() == InstallOperation::MOVE) { - move_ops++; - for (const Extent& extent : aop.op.dst_extents()) { - EXPECT_LE(7U, extent.start_block()) - << "On dst extents for aop: " << aop; - } - } - } - EXPECT_EQ(1U, move_ops); - - DumpAopsOnFailure(aops); -} - -} // namespace chromeos_update_engine diff --git a/payload_generator/mapfile_filesystem_unittest.cc b/payload_generator/mapfile_filesystem_unittest.cc index 36ae3bfa..57b672b5 100644 --- a/payload_generator/mapfile_filesystem_unittest.cc +++ b/payload_generator/mapfile_filesystem_unittest.cc @@ -55,8 +55,8 @@ void ExpectBlocksInRange(const vector<Extent>& extents, uint64_t total_blocks) { class MapfileFilesystemTest : public ::testing::Test { protected: - test_utils::ScopedTempFile temp_file_{"mapfile_file.XXXXXX"}; - test_utils::ScopedTempFile temp_mapfile_{"mapfile_mapfile.XXXXXX"}; + ScopedTempFile temp_file_{"mapfile_file.XXXXXX"}; + ScopedTempFile temp_mapfile_{"mapfile_mapfile.XXXXXX"}; }; TEST_F(MapfileFilesystemTest, EmptyFilesystem) { diff --git a/payload_generator/merge_sequence_generator.cc b/payload_generator/merge_sequence_generator.cc new file mode 100644 index 00000000..289e2f86 --- /dev/null +++ b/payload_generator/merge_sequence_generator.cc @@ -0,0 +1,301 @@ +// +// 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(); +} + +template <typename T> +constexpr T GetDifference(T first, T second) { + T abs_diff = (first > second) ? (first - second) : (second - first); + return abs_diff; +} + +void SplitSelfOverlapping(const Extent& src_extent, + const Extent& dst_extent, + std::vector<CowMergeOperation>* sequence) { + CHECK_EQ(src_extent.num_blocks(), dst_extent.num_blocks()); + if (src_extent.start_block() == dst_extent.start_block()) { + sequence->emplace_back(CreateCowMergeOperation(src_extent, dst_extent)); + return; + } + + const size_t diff = + GetDifference(src_extent.start_block(), dst_extent.start_block()); + for (size_t i = 0; i < src_extent.num_blocks(); i += diff) { + auto num_blocks = std::min<size_t>(diff, src_extent.num_blocks() - i); + sequence->emplace_back(CreateCowMergeOperation( + ExtentForRange(i + src_extent.start_block(), num_blocks), + ExtentForRange(i + dst_extent.start_block(), num_blocks))); + } +} + +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()); + + // Self-overlapping SOURCE_COPY, must split into multiple non + // self-overlapping ops + if (ExtentRanges::ExtentsOverlap(src_extent, dst_extent)) { + SplitSelfOverlapping(src_extent, dst_extent, &sequence); + } else { + 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..385fcc3c --- /dev/null +++ b/payload_generator/merge_sequence_generator.h @@ -0,0 +1,78 @@ +// +// 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_; +}; + +void SplitSelfOverlapping(const Extent& src_extent, + const Extent& dst_extent, + std::vector<CowMergeOperation>* sequence); + +} // 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..b8507ed9 --- /dev/null +++ b/payload_generator/merge_sequence_generator_unittest.cc @@ -0,0 +1,250 @@ +// +// 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_ranges.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, FindDependencyEdgeCase) { + std::vector<CowMergeOperation> transfers = { + CreateCowMergeOperation(ExtentForRange(10, 10), ExtentForRange(15, 10)), + CreateCowMergeOperation(ExtentForRange(40, 10), ExtentForRange(50, 10)), + CreateCowMergeOperation(ExtentForRange(59, 10), ExtentForRange(60, 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])); + ASSERT_EQ(merge_after[transfers[2]].size(), 1U); +} + +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); +} + +void ValidateSplitSequence(const Extent& src_extent, const Extent& dst_extent) { + std::vector<CowMergeOperation> sequence; + SplitSelfOverlapping(src_extent, dst_extent, &sequence); + ExtentRanges src_extent_set; + src_extent_set.AddExtent(src_extent); + ExtentRanges dst_extent_set; + dst_extent_set.AddExtent(dst_extent); + + size_t src_block_count = 0; + size_t dst_block_count = 0; + std::cout << "src_extent: " << src_extent << " dst_extent: " << dst_extent + << '\n'; + for (const auto& merge_op : sequence) { + src_extent_set.SubtractExtent(merge_op.src_extent()); + dst_extent_set.SubtractExtent(merge_op.dst_extent()); + src_block_count += merge_op.src_extent().num_blocks(); + dst_block_count += merge_op.dst_extent().num_blocks(); + std::cout << merge_op.src_extent() << " -> " << merge_op.dst_extent() + << '\n'; + ASSERT_FALSE(ExtentRanges::ExtentsOverlap(merge_op.src_extent(), + merge_op.dst_extent())); + } + std::cout << '\n'; + // Check that all blocks are covered + ASSERT_EQ(src_extent_set.extent_set().size(), 0UL); + ASSERT_EQ(dst_extent_set.extent_set().size(), 0UL); + + // Check that the split didn't cover extra blocks + ASSERT_EQ(src_block_count, src_extent.num_blocks()); + ASSERT_EQ(dst_block_count, dst_extent.num_blocks()); +} + +TEST_F(MergeSequenceGeneratorTest, SplitSelfOverlappingTest) { + auto a = ExtentForRange(25, 16); + auto b = ExtentForRange(30, 16); + ValidateSplitSequence(a, b); + ValidateSplitSequence(b, a); +} + +} // namespace chromeos_update_engine diff --git a/payload_generator/payload_file.cc b/payload_generator/payload_file.cc index a111fd63..6ec219ff 100644 --- a/payload_generator/payload_file.cc +++ b/payload_generator/payload_file.cc @@ -25,6 +25,7 @@ #include <base/strings/stringprintf.h> #include "update_engine/common/hash_calculator.h" +#include "update_engine/common/utils.h" #include "update_engine/payload_consumer/delta_performer.h" #include "update_engine/payload_consumer/file_writer.h" #include "update_engine/payload_consumer/payload_constants.h" @@ -64,40 +65,46 @@ bool PayloadFile::Init(const PayloadGenerationConfig& config) { TEST_AND_RETURN_FALSE(config.version.Validate()); major_version_ = config.version.major; manifest_.set_minor_version(config.version.minor); - - if (!config.source.ImageInfoIsEmpty()) - *(manifest_.mutable_old_image_info()) = config.source.image_info; - - if (!config.target.ImageInfoIsEmpty()) - *(manifest_.mutable_new_image_info()) = config.target.image_info; - manifest_.set_block_size(config.block_size); manifest_.set_max_timestamp(config.max_timestamp); - if (major_version_ == kBrilloMajorPayloadVersion) { - if (config.target.dynamic_partition_metadata != nullptr) - *(manifest_.mutable_dynamic_partition_metadata()) = - *(config.target.dynamic_partition_metadata); + if (config.target.dynamic_partition_metadata != nullptr) + *(manifest_.mutable_dynamic_partition_metadata()) = + *(config.target.dynamic_partition_metadata); + + if (config.is_partial_update) { + manifest_.set_partial_update(true); } + if (!config.apex_info_file.empty()) { + ApexMetadata apex_metadata; + int fd = open(config.apex_info_file.c_str(), O_RDONLY); + if (fd < 0) { + PLOG(FATAL) << "Failed to open " << config.apex_info_file << " for read."; + } + ScopedFdCloser closer{&fd}; + CHECK(apex_metadata.ParseFromFileDescriptor(fd)); + if (apex_metadata.apex_info_size() > 0) { + *manifest_.mutable_apex_info() = + std::move(*apex_metadata.mutable_apex_info()); + } + } return true; } bool PayloadFile::AddPartition(const PartitionConfig& old_conf, const PartitionConfig& new_conf, - const vector<AnnotatedOperation>& aops) { - // Check partitions order for Chrome OS - if (major_version_ == kChromeOSMajorPayloadVersion) { - const vector<const char*> part_order = {kPartitionNameRoot, - kPartitionNameKernel}; - TEST_AND_RETURN_FALSE(part_vec_.size() < part_order.size()); - TEST_AND_RETURN_FALSE(new_conf.name == part_order[part_vec_.size()]); - } + vector<AnnotatedOperation> aops, + vector<CowMergeOperation> merge_sequence, + size_t cow_size) { Partition part; + part.cow_size = cow_size; 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( @@ -113,11 +120,9 @@ bool PayloadFile::WritePayload(const string& payload_file, const string& private_key_path, uint64_t* metadata_size_out) { // Reorder the data blobs with the manifest_. - string ordered_blobs_path; - TEST_AND_RETURN_FALSE(utils::MakeTempFile( - "CrAU_temp_data.ordered.XXXXXX", &ordered_blobs_path, nullptr)); - ScopedPathUnlinker ordered_blobs_unlinker(ordered_blobs_path); - TEST_AND_RETURN_FALSE(ReorderDataBlobs(data_blobs_path, ordered_blobs_path)); + ScopedTempFile ordered_blobs_file("CrAU_temp_data.ordered.XXXXXX"); + TEST_AND_RETURN_FALSE( + ReorderDataBlobs(data_blobs_path, ordered_blobs_file.path())); // Check that install op blobs are in order. uint64_t next_blob_offset = 0; @@ -134,75 +139,61 @@ bool PayloadFile::WritePayload(const string& payload_file, } // Copy the operations and partition info from the part_vec_ to the manifest. - manifest_.clear_install_operations(); - manifest_.clear_kernel_install_operations(); manifest_.clear_partitions(); for (const auto& part : part_vec_) { - if (major_version_ == kBrilloMajorPayloadVersion) { - PartitionUpdate* partition = manifest_.add_partitions(); - partition->set_partition_name(part.name); - if (part.postinstall.run) { - partition->set_run_postinstall(true); - if (!part.postinstall.path.empty()) - partition->set_postinstall_path(part.postinstall.path); - if (!part.postinstall.filesystem_type.empty()) - partition->set_filesystem_type(part.postinstall.filesystem_type); - partition->set_postinstall_optional(part.postinstall.optional); - } - if (!part.verity.IsEmpty()) { - if (part.verity.hash_tree_extent.num_blocks() != 0) { - *partition->mutable_hash_tree_data_extent() = - part.verity.hash_tree_data_extent; - *partition->mutable_hash_tree_extent() = part.verity.hash_tree_extent; - partition->set_hash_tree_algorithm(part.verity.hash_tree_algorithm); - if (!part.verity.hash_tree_salt.empty()) - partition->set_hash_tree_salt(part.verity.hash_tree_salt.data(), - part.verity.hash_tree_salt.size()); - } - if (part.verity.fec_extent.num_blocks() != 0) { - *partition->mutable_fec_data_extent() = part.verity.fec_data_extent; - *partition->mutable_fec_extent() = part.verity.fec_extent; - partition->set_fec_roots(part.verity.fec_roots); - } - } - for (const AnnotatedOperation& aop : part.aops) { - *partition->add_operations() = aop.op; + PartitionUpdate* partition = manifest_.add_partitions(); + partition->set_partition_name(part.name); + if (!part.version.empty()) { + partition->set_version(part.version); + } + if (part.cow_size > 0) { + partition->set_estimate_cow_size(part.cow_size); + } + if (part.postinstall.run) { + partition->set_run_postinstall(true); + if (!part.postinstall.path.empty()) + partition->set_postinstall_path(part.postinstall.path); + if (!part.postinstall.filesystem_type.empty()) + partition->set_filesystem_type(part.postinstall.filesystem_type); + partition->set_postinstall_optional(part.postinstall.optional); + } + if (!part.verity.IsEmpty()) { + if (part.verity.hash_tree_extent.num_blocks() != 0) { + *partition->mutable_hash_tree_data_extent() = + part.verity.hash_tree_data_extent; + *partition->mutable_hash_tree_extent() = part.verity.hash_tree_extent; + partition->set_hash_tree_algorithm(part.verity.hash_tree_algorithm); + if (!part.verity.hash_tree_salt.empty()) + partition->set_hash_tree_salt(part.verity.hash_tree_salt.data(), + part.verity.hash_tree_salt.size()); } - 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()) - *(partition->mutable_new_partition_info()) = part.new_info; - } else { - // major_version_ == kChromeOSMajorPayloadVersion - if (part.name == kPartitionNameKernel) { - for (const AnnotatedOperation& aop : part.aops) - *manifest_.add_kernel_install_operations() = aop.op; - if (part.old_info.has_size() || part.old_info.has_hash()) - *manifest_.mutable_old_kernel_info() = part.old_info; - if (part.new_info.has_size() || part.new_info.has_hash()) - *manifest_.mutable_new_kernel_info() = part.new_info; - } else { - for (const AnnotatedOperation& aop : part.aops) - *manifest_.add_install_operations() = aop.op; - if (part.old_info.has_size() || part.old_info.has_hash()) - *manifest_.mutable_old_rootfs_info() = part.old_info; - if (part.new_info.has_size() || part.new_info.has_hash()) - *manifest_.mutable_new_rootfs_info() = part.new_info; + if (part.verity.fec_extent.num_blocks() != 0) { + *partition->mutable_fec_data_extent() = part.verity.fec_data_extent; + *partition->mutable_fec_extent() = part.verity.fec_extent; + partition->set_fec_roots(part.verity.fec_roots); } } + 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()) + *(partition->mutable_new_partition_info()) = part.new_info; } // Signatures appear at the end of the blobs. Note the offset in the - // manifest_. + // |manifest_|. uint64_t signature_blob_length = 0; if (!private_key_path.empty()) { TEST_AND_RETURN_FALSE(PayloadSigner::SignatureBlobLength( {private_key_path}, &signature_blob_length)); PayloadSigner::AddSignatureToManifest( - next_blob_offset, - signature_blob_length, - major_version_ == kChromeOSMajorPayloadVersion, - &manifest_); + next_blob_offset, signature_blob_length, &manifest_); } // Serialize protobuf @@ -229,18 +220,14 @@ bool PayloadFile::WritePayload(const string& payload_file, TEST_AND_RETURN_FALSE( WriteUint64AsBigEndian(&writer, serialized_manifest.size())); - // Write metadata signature size. - uint32_t metadata_signature_size = 0; - if (major_version_ == kBrilloMajorPayloadVersion) { - // Metadata signature has the same size as payload signature, because they - // are both the same kind of signature for the same kind of hash. - uint32_t metadata_signature_size = htobe32(signature_blob_length); - TEST_AND_RETURN_FALSE_ERRNO(writer.Write(&metadata_signature_size, - sizeof(metadata_signature_size))); - metadata_size += sizeof(metadata_signature_size); - // Set correct size instead of big endian size. - metadata_signature_size = signature_blob_length; - } + // Metadata signature has the same size as payload signature, because they + // are both the same kind of signature for the same kind of hash. + uint32_t metadata_signature_size = htobe32(signature_blob_length); + TEST_AND_RETURN_FALSE_ERRNO( + writer.Write(&metadata_signature_size, sizeof(metadata_signature_size))); + metadata_size += sizeof(metadata_signature_size); + // Set correct size instead of big endian size. + metadata_signature_size = signature_blob_length; // Write protobuf LOG(INFO) << "Writing final delta file protobuf... " @@ -249,8 +236,7 @@ bool PayloadFile::WritePayload(const string& payload_file, writer.Write(serialized_manifest.data(), serialized_manifest.size())); // Write metadata signature blob. - if (major_version_ == kBrilloMajorPayloadVersion && - !private_key_path.empty()) { + if (!private_key_path.empty()) { brillo::Blob metadata_hash; TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfFile( payload_file, metadata_size, &metadata_hash)); @@ -261,9 +247,9 @@ bool PayloadFile::WritePayload(const string& payload_file, writer.Write(metadata_signature.data(), metadata_signature.size())); } - // Append the data blobs + // Append the data blobs. LOG(INFO) << "Writing final delta file data blobs..."; - int blobs_fd = open(ordered_blobs_path.c_str(), O_RDONLY, 0); + int blobs_fd = open(ordered_blobs_file.path().c_str(), O_RDONLY, 0); ScopedFdCloser blobs_fd_closer(&blobs_fd); TEST_AND_RETURN_FALSE(blobs_fd >= 0); for (;;) { diff --git a/payload_generator/payload_file.h b/payload_generator/payload_file.h index 9dc80a72..3a457930 100644 --- a/payload_generator/payload_file.h +++ b/payload_generator/payload_file.h @@ -43,7 +43,9 @@ 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, + size_t cow_size); // 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 +62,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 +92,16 @@ 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; + size_t cow_size; }; std::vector<Partition> part_vec_; diff --git a/payload_generator/payload_file_unittest.cc b/payload_generator/payload_file_unittest.cc index 45faebb9..1fd36f52 100644 --- a/payload_generator/payload_file_unittest.cc +++ b/payload_generator/payload_file_unittest.cc @@ -36,7 +36,7 @@ class PayloadFileTest : public ::testing::Test { }; TEST_F(PayloadFileTest, ReorderBlobsTest) { - test_utils::ScopedTempFile orig_blobs("ReorderBlobsTest.orig.XXXXXX"); + ScopedTempFile orig_blobs("ReorderBlobsTest.orig.XXXXXX"); // The operations have three blob and one gap (the whitespace): // Rootfs operation 1: [8, 3] bcd @@ -45,7 +45,7 @@ TEST_F(PayloadFileTest, ReorderBlobsTest) { string orig_data = "kernel abcd"; EXPECT_TRUE(test_utils::WriteFileString(orig_blobs.path(), orig_data)); - test_utils::ScopedTempFile new_blobs("ReorderBlobsTest.new.XXXXXX"); + ScopedTempFile new_blobs("ReorderBlobsTest.new.XXXXXX"); payload_.part_vec_.resize(2); diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc index 88cca302..2cd2ebcc 100644 --- a/payload_generator/payload_generation_config.cc +++ b/payload_generator/payload_generation_config.cc @@ -23,6 +23,7 @@ #include <base/logging.h> #include <base/strings/string_number_conversions.h> #include <brillo/strings/string_utils.h> +#include <libsnapshot/cow_format.h> #include "update_engine/common/utils.h" #include "update_engine/payload_consumer/delta_performer.h" @@ -32,6 +33,7 @@ #include "update_engine/payload_generator/ext2_filesystem.h" #include "update_engine/payload_generator/mapfile_filesystem.h" #include "update_engine/payload_generator/raw_filesystem.h" +#include "update_engine/payload_generator/squashfs_filesystem.h" using std::string; @@ -86,6 +88,14 @@ bool PartitionConfig::OpenFilesystem() { return true; } + fs_interface = SquashfsFilesystem::CreateFromFile(path, + /*extract_deflates=*/true, + /*load_settings=*/true); + if (fs_interface) { + TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize); + return true; + } + // Fall back to a RAW filesystem. TEST_AND_RETURN_FALSE(size % kBlockSize == 0); fs_interface = RawFilesystem::Create( @@ -94,7 +104,6 @@ bool PartitionConfig::OpenFilesystem() { } bool ImageConfig::ValidateIsEmpty() const { - TEST_AND_RETURN_FALSE(ImageInfoIsEmpty()); return partitions.empty(); } @@ -168,7 +177,17 @@ bool ImageConfig::LoadDynamicPartitionMetadata( bool snapshot_enabled = false; store.GetBoolean("virtual_ab", &snapshot_enabled); metadata->set_snapshot_enabled(snapshot_enabled); - + bool vabc_enabled = false; + if (store.GetBoolean("virtual_ab_compression", &vabc_enabled) && + vabc_enabled) { + LOG(INFO) << "Target build supports VABC"; + metadata->set_vabc_enabled(vabc_enabled); + } + // We use "gz" compression by default for VABC. + if (metadata->vabc_enabled()) { + metadata->set_vabc_compression_param("gz"); + metadata->set_cow_version(android::snapshot::kCowVersionManifest); + } dynamic_partition_metadata = std::move(metadata); return true; } @@ -206,28 +225,20 @@ bool ImageConfig::ValidateDynamicPartitionMetadata() const { return true; } -bool ImageConfig::ImageInfoIsEmpty() const { - return image_info.board().empty() && image_info.key().empty() && - image_info.channel().empty() && image_info.version().empty() && - image_info.build_channel().empty() && - image_info.build_version().empty(); -} - PayloadVersion::PayloadVersion(uint64_t major_version, uint32_t minor_version) { major = major_version; minor = minor_version; } bool PayloadVersion::Validate() const { - TEST_AND_RETURN_FALSE(major == kChromeOSMajorPayloadVersion || - major == kBrilloMajorPayloadVersion); + TEST_AND_RETURN_FALSE(major == kBrilloMajorPayloadVersion); TEST_AND_RETURN_FALSE(minor == kFullPayloadMinorVersion || - minor == kInPlaceMinorPayloadVersion || minor == kSourceMinorPayloadVersion || minor == kOpSrcHashMinorPayloadVersion || minor == kBrotliBsdiffMinorPayloadVersion || minor == kPuffdiffMinorPayloadVersion || - minor == kVerityMinorPayloadVersion); + minor == kVerityMinorPayloadVersion || + minor == kPartialUpdateMinorPayloadVersion); return true; } @@ -237,13 +248,10 @@ bool PayloadVersion::OperationAllowed(InstallOperation::Type operation) const { case InstallOperation::REPLACE: case InstallOperation::REPLACE_BZ: // These operations were included in the original payload format. - return true; - case InstallOperation::REPLACE_XZ: - // These operations are included in the major version used in Brillo, but - // can also be used with minor version 3 or newer. - return major == kBrilloMajorPayloadVersion || - minor >= kOpSrcHashMinorPayloadVersion; + // These operations are included minor version 3 or newer and full + // payloads. + return true; case InstallOperation::ZERO: case InstallOperation::DISCARD: @@ -252,14 +260,6 @@ bool PayloadVersion::OperationAllowed(InstallOperation::Type operation) const { // them for delta payloads for now. return minor >= kBrotliBsdiffMinorPayloadVersion; - // Delta operations: - case InstallOperation::MOVE: - case InstallOperation::BSDIFF: - // MOVE and BSDIFF were replaced by SOURCE_COPY and SOURCE_BSDIFF and - // should not be used in newer delta versions, since the idempotent checks - // were removed. - return minor == kInPlaceMinorPayloadVersion; - case InstallOperation::SOURCE_COPY: case InstallOperation::SOURCE_BSDIFF: return minor >= kSourceMinorPayloadVersion; @@ -269,21 +269,22 @@ bool PayloadVersion::OperationAllowed(InstallOperation::Type operation) const { case InstallOperation::PUFFDIFF: return minor >= kPuffdiffMinorPayloadVersion; + + case InstallOperation::MOVE: + case InstallOperation::BSDIFF: + NOTREACHED(); } return false; } -bool PayloadVersion::IsDelta() const { +bool PayloadVersion::IsDeltaOrPartial() const { return minor != kFullPayloadMinorVersion; } -bool PayloadVersion::InplaceUpdate() const { - return minor == kInPlaceMinorPayloadVersion; -} - 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()) { @@ -295,9 +296,6 @@ bool PayloadGenerationConfig::Validate() const { TEST_AND_RETURN_FALSE(part.verity.IsEmpty()); } - // If new_image_info is present, old_image_info must be present. - TEST_AND_RETURN_FALSE(source.ImageInfoIsEmpty() == - target.ImageInfoIsEmpty()); } else { // All the "source" image fields must be empty for full payloads. TEST_AND_RETURN_FALSE(source.ValidateIsEmpty()); @@ -307,15 +305,14 @@ bool PayloadGenerationConfig::Validate() const { for (const PartitionConfig& part : target.partitions) { TEST_AND_RETURN_FALSE(part.ValidateExists()); TEST_AND_RETURN_FALSE(part.size % block_size == 0); - if (version.minor == kInPlaceMinorPayloadVersion && - part.name == kPartitionNameRoot) - TEST_AND_RETURN_FALSE(rootfs_partition_size >= part.size); - if (version.major == kChromeOSMajorPayloadVersion) - TEST_AND_RETURN_FALSE(part.postinstall.IsEmpty()); if (version.minor < kVerityMinorPayloadVersion) 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 e90edde7..9c8c59f9 100644 --- a/payload_generator/payload_generation_config.h +++ b/payload_generator/payload_generation_config.h @@ -119,6 +119,9 @@ struct PartitionConfig { // 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 @@ -146,13 +149,6 @@ struct ImageConfig { // Validate |dynamic_partition_metadata| against |partitions|. bool ValidateDynamicPartitionMetadata() const; - // Returns whether the |image_info| field is empty. - bool ImageInfoIsEmpty() const; - - // The ImageInfo message defined in the update_metadata.proto file describes - // the metadata of the image. - ImageInfo image_info; - // The updated partitions. std::vector<PartitionConfig> partitions; @@ -170,12 +166,8 @@ struct PayloadVersion { // Return whether the passed |operation| is allowed by this payload. bool OperationAllowed(InstallOperation::Type operation) const; - // Whether this payload version is a delta payload. - bool IsDelta() const; - - // Tells whether the update is done in-place, that is, whether the operations - // read and write from the same partition. - bool InplaceUpdate() const; + // Whether this payload version is a delta or partial payload. + bool IsDeltaOrPartial() const; // The major version of the payload. uint64_t major; @@ -202,6 +194,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; @@ -234,6 +230,9 @@ struct PayloadGenerationConfig { // The maximum timestamp of the OS allowed to apply this payload. int64_t max_timestamp = 0; + + // Path to apex_info.pb, extracted from target_file.zip + std::string apex_info_file; }; } // namespace chromeos_update_engine diff --git a/payload_generator/payload_generation_config_android_unittest.cc b/payload_generator/payload_generation_config_android_unittest.cc index 44eaf55e..e87b034f 100644 --- a/payload_generator/payload_generation_config_android_unittest.cc +++ b/payload_generator/payload_generation_config_android_unittest.cc @@ -138,8 +138,7 @@ class PayloadGenerationConfigAndroidTest : public ::testing::Test { } ImageConfig image_config_; - test_utils::ScopedTempFile temp_file_{ - "PayloadGenerationConfigAndroidTest.XXXXXX"}; + ScopedTempFile temp_file_{"PayloadGenerationConfigAndroidTest.XXXXXX"}; }; TEST_F(PayloadGenerationConfigAndroidTest, LoadVerityConfigSimpleTest) { diff --git a/payload_generator/payload_properties.cc b/payload_generator/payload_properties.cc new file mode 100644 index 00000000..bcf4fbda --- /dev/null +++ b/payload_generator/payload_properties.cc @@ -0,0 +1,124 @@ +// +// Copyright 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_generator/payload_properties.h" + +#include <algorithm> +#include <string> +#include <utility> +#include <vector> + +#include <base/json/json_writer.h> +#include <base/strings/string_util.h> +#include <base/values.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_metadata.h" +#include "update_engine/update_metadata.pb.h" + +using std::string; +using std::vector; + +namespace chromeos_update_engine { + +namespace { +// These ones are needed by the GoldenEye. +const char kPayloadPropertyJsonVersion[] = "version"; +const char kPayloadPropertyJsonPayloadHash[] = "sha256_hex"; +const char kPayloadPropertyJsonMetadataSize[] = "metadata_size"; +const char kPayloadPropertyJsonMetadataSignature[] = "metadata_signature"; + +// These are needed by the Nebraska and devserver. +const char kPayloadPropertyJsonPayloadSize[] = "size"; +const char kPayloadPropertyJsonIsDelta[] = "is_delta"; +} // namespace + +PayloadProperties::PayloadProperties(const string& payload_path) + : payload_path_(payload_path) {} + +bool PayloadProperties::GetPropertiesAsJson(string* json_str) { + TEST_AND_RETURN_FALSE(LoadFromPayload()); + + base::DictionaryValue properties; + properties.SetInteger(kPayloadPropertyJsonVersion, version_); + properties.SetInteger(kPayloadPropertyJsonMetadataSize, metadata_size_); + properties.SetString(kPayloadPropertyJsonMetadataSignature, + metadata_signatures_); + properties.SetInteger(kPayloadPropertyJsonPayloadSize, payload_size_); + properties.SetString(kPayloadPropertyJsonPayloadHash, payload_hash_); + properties.SetBoolean(kPayloadPropertyJsonIsDelta, is_delta_); + + return base::JSONWriter::Write(properties, json_str); +} + +bool PayloadProperties::GetPropertiesAsKeyValue(string* key_value_str) { + TEST_AND_RETURN_FALSE(LoadFromPayload()); + + brillo::KeyValueStore properties; + properties.SetString(kPayloadPropertyFileSize, std::to_string(payload_size_)); + properties.SetString(kPayloadPropertyMetadataSize, + std::to_string(metadata_size_)); + properties.SetString(kPayloadPropertyFileHash, payload_hash_); + properties.SetString(kPayloadPropertyMetadataHash, metadata_hash_); + + *key_value_str = properties.SaveToString(); + return true; +} + +bool PayloadProperties::LoadFromPayload() { + PayloadMetadata payload_metadata; + DeltaArchiveManifest manifest; + Signatures metadata_signatures; + TEST_AND_RETURN_FALSE(payload_metadata.ParsePayloadFile( + payload_path_, &manifest, &metadata_signatures)); + + metadata_size_ = payload_metadata.GetMetadataSize(); + payload_size_ = utils::FileSize(payload_path_); + + brillo::Blob metadata_hash; + TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfFile( + payload_path_, metadata_size_, &metadata_hash) == + static_cast<off_t>(metadata_size_)); + metadata_hash_ = brillo::data_encoding::Base64Encode(metadata_hash); + + brillo::Blob payload_hash; + TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfFile( + payload_path_, payload_size_, &payload_hash) == + static_cast<off_t>(payload_size_)); + payload_hash_ = brillo::data_encoding::Base64Encode(payload_hash); + + if (payload_metadata.GetMetadataSignatureSize() > 0) { + TEST_AND_RETURN_FALSE(metadata_signatures.signatures_size() > 0); + vector<string> base64_signatures; + for (const auto& sig : metadata_signatures.signatures()) { + base64_signatures.push_back( + brillo::data_encoding::Base64Encode(sig.data())); + } + metadata_signatures_ = base::JoinString(base64_signatures, ":"); + } + + is_delta_ = std::any_of(manifest.partitions().begin(), + manifest.partitions().end(), + [](const PartitionUpdate& part) { + return part.has_old_partition_info(); + }); + return true; +} + +} // namespace chromeos_update_engine diff --git a/payload_generator/payload_properties.h b/payload_generator/payload_properties.h new file mode 100644 index 00000000..846b181d --- /dev/null +++ b/payload_generator/payload_properties.h @@ -0,0 +1,70 @@ +// +// Copyright 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_PAYLOAD_GENERATOR_PAYLOAD_PROPERTIES_H_ +#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_PROPERTIES_H_ + +#include <string> + +#include <brillo/key_value_store.h> +#include <brillo/secure_blob.h> + +namespace chromeos_update_engine { + +// A class for extracting information about a payload from the payload file +// itself. Currently the metadata can be exported as a json file or a key/value +// properties file. But more can be added if required. +class PayloadProperties { + public: + explicit PayloadProperties(const std::string& payload_path); + ~PayloadProperties() = default; + + // Get the properties in a json format. The json file will be used in + // autotests, cros flash, etc. Mainly in Chrome OS. + bool GetPropertiesAsJson(std::string* json_str); + + // Get the properties of the payload as a key/value store. This is mainly used + // in Android. + bool GetPropertiesAsKeyValue(std::string* key_value_str); + + private: + // Does the main job of reading the payload and extracting information from + // it. + bool LoadFromPayload(); + + // The path to the payload file. + std::string payload_path_; + + // The version of the metadata json format. If the output json file changes + // format, this needs to be increased. + int version_{2}; + + size_t metadata_size_; + std::string metadata_hash_; + std::string metadata_signatures_; + + size_t payload_size_; + std::string payload_hash_; + + // Whether the payload is a delta (true) or full (false). + bool is_delta_; + + DISALLOW_COPY_AND_ASSIGN(PayloadProperties); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_PROPERTIES_H_ diff --git a/payload_generator/payload_properties_unittest.cc b/payload_generator/payload_properties_unittest.cc new file mode 100644 index 00000000..0ff364fc --- /dev/null +++ b/payload_generator/payload_properties_unittest.cc @@ -0,0 +1,132 @@ +// +// 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_generator/payload_properties.h" + +#include <memory> +#include <string> +#include <vector> + +#include <base/files/file_util.h> +#include <base/files/scoped_file.h> +#include <base/files/scoped_temp_dir.h> +#include <base/rand_util.h> +#include <base/strings/stringprintf.h> +#include <brillo/data_encoding.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/install_plan.h" +#include "update_engine/payload_generator/delta_diff_generator.h" +#include "update_engine/payload_generator/delta_diff_utils.h" +#include "update_engine/payload_generator/full_update_generator.h" +#include "update_engine/payload_generator/operations_generator.h" +#include "update_engine/payload_generator/payload_file.h" +#include "update_engine/payload_generator/payload_generation_config.h" + +using std::string; +using std::unique_ptr; +using std::vector; + +namespace chromeos_update_engine { + +// TODO(kimjae): current implementation is very specific to a static way of +// producing a deterministic test. It would definitely be beneficial to +// extend the |PayloadPropertiesTest::SetUp()| into a generic helper or +// seperate class that can handle creation of different |PayloadFile|s. +class PayloadPropertiesTest : public ::testing::Test { + protected: + void SetUp() override { + PayloadGenerationConfig config; + config.version.major = kBrilloMajorPayloadVersion; + config.version.minor = kSourceMinorPayloadVersion; + PayloadFile payload; + EXPECT_TRUE(payload.Init(config)); + + const auto SetupPartitionConfig = + [](PartitionConfig* config, const string& path, size_t size) { + config->path = path; + config->size = size; + }; + const auto WriteZerosToFile = [](const char path[], size_t size) { + string zeros(size, '\0'); + EXPECT_TRUE(utils::WriteFile(path, zeros.c_str(), zeros.size())); + }; + ScopedTempFile old_part_file("old_part.XXXXXX"); + ScopedTempFile new_part_file("new_part.XXXXXX"); + PartitionConfig old_part(kPartitionNameRoot); + PartitionConfig new_part(kPartitionNameRoot); + SetupPartitionConfig(&old_part, old_part_file.path(), 0); + SetupPartitionConfig(&new_part, new_part_file.path(), 10000); + WriteZerosToFile(old_part_file.path().c_str(), old_part.size); + WriteZerosToFile(new_part_file.path().c_str(), new_part.size); + + // Select payload generation strategy based on the config. + unique_ptr<OperationsGenerator> strategy(new FullUpdateGenerator()); + + vector<AnnotatedOperation> aops; + off_t data_file_size = 0; + ScopedTempFile data_file("temp_data.XXXXXX", true); + BlobFileWriter blob_file_writer(data_file.fd(), &data_file_size); + // Generate the operations using the strategy we selected above. + EXPECT_TRUE(strategy->GenerateOperations( + config, old_part, new_part, &blob_file_writer, &aops)); + + payload.AddPartition(old_part, new_part, aops, {}, 0); + + uint64_t metadata_size; + EXPECT_TRUE(payload.WritePayload( + payload_file_.path(), data_file.path(), "", &metadata_size)); + } + + ScopedTempFile payload_file_{"payload_file.XXXXXX"}; +}; + +// Validate the hash of file exists within the output. +TEST_F(PayloadPropertiesTest, GetPropertiesAsJsonTestHash) { + constexpr char kJsonProperties[] = + "{" + R"("is_delta":true,)" + R"("metadata_signature":"",)" + R"("metadata_size":165,)" + R"("sha256_hex":"cV7kfZBH3K0B6QJHxxykDh6b6x0WgVOmc63whPLOy7U=",)" + R"("size":211,)" + R"("version":2)" + "}"; + string json; + EXPECT_TRUE( + PayloadProperties(payload_file_.path()).GetPropertiesAsJson(&json)); + EXPECT_EQ(kJsonProperties, json) << "JSON contents:\n" << json; +} + +// Validate the hash of file and metadata are within the output. +TEST_F(PayloadPropertiesTest, GetPropertiesAsKeyValueTestHash) { + constexpr char kKeyValueProperties[] = + "FILE_HASH=cV7kfZBH3K0B6QJHxxykDh6b6x0WgVOmc63whPLOy7U=\n" + "FILE_SIZE=211\n" + "METADATA_HASH=aEKYyzJt2E8Gz8fzB+gmekN5mriotZCSq6R+kDfdeV4=\n" + "METADATA_SIZE=165\n"; + string key_value; + EXPECT_TRUE(PayloadProperties{payload_file_.path()}.GetPropertiesAsKeyValue( + &key_value)); + EXPECT_EQ(kKeyValueProperties, key_value) << "Key Value contents:\n" + << key_value; +} + +} // namespace chromeos_update_engine diff --git a/payload_generator/payload_signer.cc b/payload_generator/payload_signer.cc index 72780b12..d9f0dd77 100644 --- a/payload_generator/payload_signer.cc +++ b/payload_generator/payload_signer.cc @@ -82,7 +82,7 @@ bool ConvertSignaturesToProtobuf(const vector<brillo::Blob>& signatures, // 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| // if the payload major version supports metadata signature, 0 otherwise. @@ -104,22 +104,20 @@ bool AddSignatureBlobToPayload(const string& payload_path, uint64_t metadata_size = payload_metadata.GetMetadataSize(); uint32_t metadata_signature_size = payload_metadata.GetMetadataSignatureSize(); - if (payload_metadata.GetMajorVersion() == kBrilloMajorPayloadVersion) { - // Write metadata signature size in header. - uint32_t metadata_signature_size_be = htobe32(metadata_signature.size()); - memcpy(payload.data() + manifest_offset, - &metadata_signature_size_be, - sizeof(metadata_signature_size_be)); - manifest_offset += sizeof(metadata_signature_size_be); - // Replace metadata signature. - payload.erase(payload.begin() + metadata_size, - payload.begin() + metadata_size + metadata_signature_size); - payload.insert(payload.begin() + metadata_size, - metadata_signature.begin(), - metadata_signature.end()); - metadata_signature_size = metadata_signature.size(); - LOG(INFO) << "Metadata signature size: " << metadata_signature_size; - } + // Write metadata signature size in header. + uint32_t metadata_signature_size_be = htobe32(metadata_signature.size()); + memcpy(payload.data() + manifest_offset, + &metadata_signature_size_be, + sizeof(metadata_signature_size_be)); + manifest_offset += sizeof(metadata_signature_size_be); + // Replace metadata signature. + payload.erase(payload.begin() + metadata_size, + payload.begin() + metadata_size + metadata_signature_size); + payload.insert(payload.begin() + metadata_size, + metadata_signature.begin(), + metadata_signature.end()); + metadata_signature_size = metadata_signature.size(); + LOG(INFO) << "Metadata signature size: " << metadata_signature_size; DeltaArchiveManifest manifest; TEST_AND_RETURN_FALSE(payload_metadata.GetManifest(payload, &manifest)); @@ -143,7 +141,6 @@ bool AddSignatureBlobToPayload(const string& payload_path, PayloadSigner::AddSignatureToManifest( payload.size() - metadata_size - metadata_signature_size, payload_signature.size(), - payload_metadata.GetMajorVersion() == kChromeOSMajorPayloadVersion, &manifest); // Updates the payload to include the new manifest. @@ -241,25 +238,10 @@ bool PayloadSigner::GetMaximumSignatureSize(const string& private_key_path, void PayloadSigner::AddSignatureToManifest(uint64_t signature_blob_offset, uint64_t signature_blob_length, - bool add_dummy_op, DeltaArchiveManifest* manifest) { LOG(INFO) << "Making room for signature in file"; manifest->set_signatures_offset(signature_blob_offset); - LOG(INFO) << "set? " << manifest->has_signatures_offset(); - manifest->set_signatures_offset(signature_blob_offset); manifest->set_signatures_size(signature_blob_length); - // Add a dummy op at the end to appease older clients - if (add_dummy_op) { - InstallOperation* dummy_op = manifest->add_kernel_install_operations(); - dummy_op->set_type(InstallOperation::REPLACE); - dummy_op->set_data_offset(signature_blob_offset); - dummy_op->set_data_length(signature_blob_length); - Extent* dummy_extent = dummy_op->add_dst_extents(); - // Tell the dummy op to write this data to a big sparse hole - dummy_extent->set_start_block(kSparseHole); - dummy_extent->set_num_blocks( - utils::DivRoundUp(signature_blob_length, kBlockSize)); - } } bool PayloadSigner::VerifySignedPayload(const string& payload_path, @@ -337,7 +319,6 @@ bool PayloadSigner::SignHash(const brillo::Blob& hash, signature.data(), rsa, RSA_NO_PADDING); - if (signature_size < 0) { LOG(ERROR) << "Signing hash failed: " << ERR_error_string(ERR_get_error(), nullptr); @@ -512,35 +493,4 @@ bool PayloadSigner::GetMetadataSignature(const void* const metadata, return true; } -bool PayloadSigner::ExtractPayloadProperties( - const string& payload_path, brillo::KeyValueStore* properties) { - brillo::Blob payload; - TEST_AND_RETURN_FALSE( - utils::ReadFileChunk(payload_path, 0, kMaxPayloadHeaderSize, &payload)); - - PayloadMetadata payload_metadata; - TEST_AND_RETURN_FALSE(payload_metadata.ParsePayloadHeader(payload)); - uint64_t metadata_size = payload_metadata.GetMetadataSize(); - - uint64_t file_size = utils::FileSize(payload_path); - properties->SetString(kPayloadPropertyFileSize, std::to_string(file_size)); - properties->SetString(kPayloadPropertyMetadataSize, - std::to_string(metadata_size)); - - brillo::Blob file_hash, metadata_hash; - TEST_AND_RETURN_FALSE( - HashCalculator::RawHashOfFile(payload_path, file_size, &file_hash) == - static_cast<off_t>(file_size)); - - TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfFile( - payload_path, metadata_size, &metadata_hash) == - static_cast<off_t>(metadata_size)); - - properties->SetString(kPayloadPropertyFileHash, - brillo::data_encoding::Base64Encode(file_hash)); - properties->SetString(kPayloadPropertyMetadataHash, - brillo::data_encoding::Base64Encode(metadata_hash)); - return true; -} - } // namespace chromeos_update_engine diff --git a/payload_generator/payload_signer.h b/payload_generator/payload_signer.h index bd1e32f6..9676b718 100644 --- a/payload_generator/payload_signer.h +++ b/payload_generator/payload_signer.h @@ -39,12 +39,9 @@ class PayloadSigner { static bool VerifySignedPayload(const std::string& payload_path, const std::string& public_key_path); - // Adds specified signature offset/length to given |manifest|, also adds a - // dummy operation that points to a signature blob located at the specified - // offset/length if |add_dummy_op| is true. + // Adds specified signature offset/length to given |manifest|. static void AddSignatureToManifest(uint64_t signature_blob_offset, uint64_t signature_blob_length, - bool add_dummy_op, DeltaArchiveManifest* manifest); // Given a raw |hash| and a private key in |private_key_path| calculates the @@ -65,7 +62,7 @@ class PayloadSigner { // size in |metadata_signature_size| and signatures offset in // |signatures_offset|, calculates the payload signature blob into // |out_serialized_signature|. Note that the payload must already have an - // updated manifest that includes the dummy signature op and correct metadata + // 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, @@ -95,7 +92,7 @@ class PayloadSigner { 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 diff --git a/payload_generator/payload_signer_unittest.cc b/payload_generator/payload_signer_unittest.cc index bf7100b5..2a0b394c 100644 --- a/payload_generator/payload_signer_unittest.cc +++ b/payload_generator/payload_signer_unittest.cc @@ -20,6 +20,7 @@ #include <vector> #include <base/logging.h> +#include <base/stl_util.h> #include <gtest/gtest.h> #include "update_engine/common/hash_calculator.h" @@ -118,8 +119,8 @@ TEST_F(PayloadSignerTest, SignSimpleTextTest) { EXPECT_EQ(1, signatures.signatures_size()); const Signatures::Signature& sig = signatures.signatures(0); const string& sig_data = sig.data(); - ASSERT_EQ(arraysize(kDataSignature), sig_data.size()); - for (size_t i = 0; i < arraysize(kDataSignature); i++) { + 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])); } } @@ -166,7 +167,7 @@ TEST_F(PayloadSignerTest, VerifySignatureTest) { } TEST_F(PayloadSignerTest, SkipMetadataSignatureTest) { - test_utils::ScopedTempFile payload_file("payload.XXXXXX"); + ScopedTempFile payload_file("payload.XXXXXX"); PayloadGenerationConfig config; config.version.major = kBrilloMajorPayloadVersion; PayloadFile payload; @@ -193,7 +194,7 @@ TEST_F(PayloadSignerTest, SkipMetadataSignatureTest) { } TEST_F(PayloadSignerTest, VerifySignedPayloadTest) { - test_utils::ScopedTempFile payload_file("payload.XXXXXX"); + ScopedTempFile payload_file("payload.XXXXXX"); PayloadGenerationConfig config; config.version.major = kBrilloMajorPayloadVersion; PayloadFile payload; diff --git a/payload_generator/squashfs_filesystem.cc b/payload_generator/squashfs_filesystem.cc index 6c892f53..a41e2830 100644 --- a/payload_generator/squashfs_filesystem.cc +++ b/payload_generator/squashfs_filesystem.cc @@ -23,6 +23,7 @@ #include <utility> #include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> #include <base/logging.h> #include <base/strings/string_number_conversions.h> #include <base/strings/string_split.h> @@ -36,6 +37,8 @@ #include "update_engine/payload_generator/extent_utils.h" #include "update_engine/update_metadata.pb.h" +using base::FilePath; +using base::ScopedTempDir; using std::string; using std::unique_ptr; using std::vector; @@ -49,6 +52,8 @@ constexpr size_t kSquashfsSuperBlockSize = 96; constexpr uint64_t kSquashfsCompressedBit = 1 << 24; constexpr uint32_t kSquashfsZlibCompression = 1; +constexpr char kUpdateEngineConf[] = "etc/update_engine.conf"; + bool ReadSquashfsHeader(const brillo::Blob blob, SquashfsFilesystem::SquashfsHeader* header) { if (blob.size() < kSquashfsSuperBlockSize) { @@ -67,24 +72,60 @@ bool CheckHeader(const SquashfsFilesystem::SquashfsHeader& header) { } bool GetFileMapContent(const string& sqfs_path, string* map) { - // Create a tmp file - string map_file; - TEST_AND_RETURN_FALSE( - utils::MakeTempFile("squashfs_file_map.XXXXXX", &map_file, nullptr)); - ScopedPathUnlinker map_unlinker(map_file); - + ScopedTempFile map_file("squashfs_file_map.XXXXXX"); // Run unsquashfs to get the system file map. // unsquashfs -m <map-file> <squashfs-file> - vector<string> cmd = {"unsquashfs", "-m", map_file, sqfs_path}; - string stdout; + vector<string> cmd = {"unsquashfs", "-m", map_file.path(), sqfs_path}; + string stdout, stderr; int exit_code; - if (!Subprocess::SynchronousExec(cmd, &exit_code, &stdout) || + if (!Subprocess::SynchronousExec(cmd, &exit_code, &stdout, &stderr) || exit_code != 0) { - LOG(ERROR) << "Failed to run unsquashfs -m. The stdout content was: " - << stdout; + LOG(ERROR) << "Failed to run `unsquashfs -m` with stdout content: " + << stdout << " and stderr content: " << stderr; + return false; + } + TEST_AND_RETURN_FALSE(utils::ReadFile(map_file.path(), map)); + return true; +} + +bool GetUpdateEngineConfig(const std::string& sqfs_path, string* config) { + ScopedTempDir unsquash_dir; + if (!unsquash_dir.CreateUniqueTempDir()) { + PLOG(ERROR) << "Failed to create a temporary directory."; + return false; + } + + // Run unsquashfs to extract update_engine.conf + // -f: To force overriding if the target directory exists. + // -d: The directory to unsquash the files. + vector<string> cmd = {"unsquashfs", + "-f", + "-d", + unsquash_dir.GetPath().value(), + sqfs_path, + kUpdateEngineConf}; + string stdout, stderr; + int exit_code; + if (!Subprocess::SynchronousExec(cmd, &exit_code, &stdout, &stderr) || + exit_code != 0) { + PLOG(ERROR) << "Failed to unsquashfs etc/update_engine.conf with stdout: " + << stdout << " and stderr: " << stderr; + return false; + } + + auto config_path = unsquash_dir.GetPath().Append(kUpdateEngineConf); + string config_content; + if (!utils::ReadFile(config_path.value(), &config_content)) { + PLOG(ERROR) << "Failed to read " << config_path.value(); return false; } - TEST_AND_RETURN_FALSE(utils::ReadFile(map_file, map)); + + if (config_content.empty()) { + LOG(ERROR) << "update_engine config file was empty!!"; + return false; + } + + *config = std::move(config_content); return true; } @@ -120,6 +161,7 @@ bool SquashfsFilesystem::Init(const string& map, uint64_t start; TEST_AND_RETURN_FALSE(base::StringToUint64(splits[1], &start)); uint64_t cur_offset = start; + bool is_compressed = false; for (size_t i = 2; i < splits.size(); ++i) { uint64_t blk_size; TEST_AND_RETURN_FALSE(base::StringToUint64(splits[i], &blk_size)); @@ -127,10 +169,11 @@ bool SquashfsFilesystem::Init(const string& map, auto new_blk_size = blk_size & ~kSquashfsCompressedBit; TEST_AND_RETURN_FALSE(new_blk_size <= header.block_size); if (new_blk_size > 0 && !(blk_size & kSquashfsCompressedBit)) { - // Compressed block + // It is a compressed block. if (is_zlib && extract_deflates) { zlib_blks.emplace_back(cur_offset, new_blk_size); } + is_compressed = true; } cur_offset += new_blk_size; } @@ -140,6 +183,7 @@ bool SquashfsFilesystem::Init(const string& map, File file; file.name = splits[0].as_string(); file.extents = {ExtentForBytes(kBlockSize, start, cur_offset - start)}; + file.is_compressed = is_compressed; files_.emplace_back(file); } } @@ -151,7 +195,8 @@ bool SquashfsFilesystem::Init(const string& map, // If there is any overlap between two consecutive extents, remove them. Here // we are assuming all files have exactly one extent. If this assumption // changes then this implementation needs to change too. - for (auto first = files_.begin(), second = first + 1; + for (auto first = files_.begin(), + second = first + (first == files_.end() ? 0 : 1); first != files_.end() && second != files_.end(); second = first + 1) { auto first_begin = first->extents[0].start_block(); @@ -217,7 +262,15 @@ bool SquashfsFilesystem::Init(const string& map, return a.offset < b.offset; }); - // Sanity check. Make sure zlib blocks are not overlapping. + // Sometimes a squashfs can have a two files that are hard linked. In this + // case both files will have the same starting offset in the image and hence + // the same zlib blocks. So we need to remove these duplicates to eliminate + // further potential probems. As a matter of fact the next statement will + // fail if there are duplicates (there will be overlap between two blocks). + auto last = std::unique(zlib_blks.begin(), zlib_blks.end()); + zlib_blks.erase(last, zlib_blks.end()); + + // Make sure zlib blocks are not overlapping. auto result = std::adjacent_find( zlib_blks.begin(), zlib_blks.end(), @@ -239,12 +292,12 @@ bool SquashfsFilesystem::Init(const string& map, } unique_ptr<SquashfsFilesystem> SquashfsFilesystem::CreateFromFile( - const string& sqfs_path, bool extract_deflates) { + const string& sqfs_path, bool extract_deflates, bool load_settings) { if (sqfs_path.empty()) return nullptr; brillo::StreamPtr sqfs_file = - brillo::FileStream::Open(base::FilePath(sqfs_path), + brillo::FileStream::Open(FilePath(sqfs_path), brillo::Stream::AccessMode::READ, brillo::FileStream::Disposition::OPEN_EXISTING, nullptr); @@ -278,6 +331,12 @@ unique_ptr<SquashfsFilesystem> SquashfsFilesystem::CreateFromFile( return nullptr; } + if (load_settings) { + if (!GetUpdateEngineConfig(sqfs_path, &sqfs->update_engine_config_)) { + return nullptr; + } + } + return sqfs; } @@ -311,9 +370,12 @@ bool SquashfsFilesystem::GetFiles(vector<File>* files) const { } bool SquashfsFilesystem::LoadSettings(brillo::KeyValueStore* store) const { - // Settings not supported in squashfs. - LOG(ERROR) << "squashfs doesn't support LoadSettings()."; - return false; + if (!store->LoadFromString(update_engine_config_)) { + LOG(ERROR) << "Failed to load the settings with config: " + << update_engine_config_; + return false; + } + return true; } bool SquashfsFilesystem::IsSquashfsImage(const brillo::Blob& blob) { diff --git a/payload_generator/squashfs_filesystem.h b/payload_generator/squashfs_filesystem.h index b79f8c7d..5045dfc6 100644 --- a/payload_generator/squashfs_filesystem.h +++ b/payload_generator/squashfs_filesystem.h @@ -59,7 +59,7 @@ class SquashfsFilesystem : public FilesystemInterface { // |extract_deflates| is true, it will process files to find location of all // deflate streams. static std::unique_ptr<SquashfsFilesystem> CreateFromFile( - const std::string& sqfs_path, bool extract_deflates); + const std::string& sqfs_path, bool extract_deflates, bool load_settings); // Creates the file system from a file map |filemap| which is a multi-line // string with each line with the following format: @@ -113,6 +113,9 @@ class SquashfsFilesystem : public FilesystemInterface { // All the files in the filesystem. std::vector<File> files_; + // The content of /etc/update_engine.conf. + std::string update_engine_config_; + DISALLOW_COPY_AND_ASSIGN(SquashfsFilesystem); }; diff --git a/payload_generator/squashfs_filesystem_unittest.cc b/payload_generator/squashfs_filesystem_unittest.cc index 29fcf1cd..68ca9df2 100644 --- a/payload_generator/squashfs_filesystem_unittest.cc +++ b/payload_generator/squashfs_filesystem_unittest.cc @@ -112,7 +112,7 @@ class SquashfsFilesystemTest : public ::testing::Test { #ifdef __CHROMEOS__ TEST_F(SquashfsFilesystemTest, EmptyFilesystemTest) { unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile( - GetBuildArtifactsPath("gen/disk_sqfs_empty.img"), true); + GetBuildArtifactsPath("gen/disk_sqfs_empty.img"), true, false); CheckSquashfs(fs); // Even an empty squashfs filesystem is rounded up to 4K. @@ -133,7 +133,7 @@ TEST_F(SquashfsFilesystemTest, EmptyFilesystemTest) { TEST_F(SquashfsFilesystemTest, DefaultFilesystemTest) { unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile( - GetBuildArtifactsPath("gen/disk_sqfs_default.img"), true); + GetBuildArtifactsPath("gen/disk_sqfs_default.img"), true, false); CheckSquashfs(fs); vector<FilesystemInterface::File> files; @@ -148,6 +148,18 @@ TEST_F(SquashfsFilesystemTest, DefaultFilesystemTest) { EXPECT_EQ(files[0].name, file.name); EXPECT_EQ(files[0].extents, file.extents); } + +TEST_F(SquashfsFilesystemTest, UpdateEngineConfigTest) { + unique_ptr<SquashfsFilesystem> fs = SquashfsFilesystem::CreateFromFile( + GetBuildArtifactsPath("gen/disk_sqfs_unittest.img"), true, true); + CheckSquashfs(fs); + + brillo::KeyValueStore kvs; + EXPECT_TRUE(fs->LoadSettings(&kvs)); + string minor_version; + EXPECT_TRUE(kvs.GetString("PAYLOAD_MINOR_VERSION", &minor_version)); + EXPECT_EQ(minor_version, "1234"); +} #endif // __CHROMEOS__ TEST_F(SquashfsFilesystemTest, SimpleFileMapTest) { diff --git a/payload_generator/tarjan.cc b/payload_generator/tarjan.cc deleted file mode 100644 index 2d4ca316..00000000 --- a/payload_generator/tarjan.cc +++ /dev/null @@ -1,83 +0,0 @@ -// -// Copyright (C) 2010 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/tarjan.h" - -#include <algorithm> -#include <vector> - -#include <base/logging.h> -#include <base/stl_util.h> - -using std::min; -using std::vector; - -namespace chromeos_update_engine { - -namespace { -const vector<Vertex>::size_type kInvalidIndex = -1; -} - -void TarjanAlgorithm::Execute(Vertex::Index vertex, - Graph* graph, - vector<Vertex::Index>* out) { - stack_.clear(); - components_.clear(); - index_ = 0; - for (Graph::iterator it = graph->begin(); it != graph->end(); ++it) - it->index = it->lowlink = kInvalidIndex; - required_vertex_ = vertex; - - Tarjan(vertex, graph); - if (!components_.empty()) - out->swap(components_[0]); -} - -void TarjanAlgorithm::Tarjan(Vertex::Index vertex, Graph* graph) { - CHECK_EQ((*graph)[vertex].index, kInvalidIndex); - (*graph)[vertex].index = index_; - (*graph)[vertex].lowlink = index_; - index_++; - stack_.push_back(vertex); - for (Vertex::EdgeMap::iterator it = (*graph)[vertex].out_edges.begin(); - it != (*graph)[vertex].out_edges.end(); - ++it) { - Vertex::Index vertex_next = it->first; - if ((*graph)[vertex_next].index == kInvalidIndex) { - Tarjan(vertex_next, graph); - (*graph)[vertex].lowlink = - min((*graph)[vertex].lowlink, (*graph)[vertex_next].lowlink); - } else if (base::ContainsValue(stack_, vertex_next)) { - (*graph)[vertex].lowlink = - min((*graph)[vertex].lowlink, (*graph)[vertex_next].index); - } - } - if ((*graph)[vertex].lowlink == (*graph)[vertex].index) { - vector<Vertex::Index> component; - Vertex::Index other_vertex; - do { - other_vertex = stack_.back(); - stack_.pop_back(); - component.push_back(other_vertex); - } while (other_vertex != vertex && !stack_.empty()); - - if (base::ContainsValue(component, required_vertex_)) { - components_.resize(components_.size() + 1); - component.swap(components_.back()); - } - } -} - -} // namespace chromeos_update_engine diff --git a/payload_generator/tarjan.h b/payload_generator/tarjan.h deleted file mode 100644 index 39ac4e4e..00000000 --- a/payload_generator/tarjan.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright (C) 2010 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_TARJAN_H_ -#define UPDATE_ENGINE_PAYLOAD_GENERATOR_TARJAN_H_ - -// This is an implementation of Tarjan's algorithm which finds all -// Strongly Connected Components in a graph. - -// Note: a true Tarjan algorithm would find all strongly connected components -// in the graph. This implementation will only find the strongly connected -// component containing the vertex passed in. - -#include <vector> - -#include "update_engine/payload_generator/graph_types.h" - -namespace chromeos_update_engine { - -class TarjanAlgorithm { - public: - TarjanAlgorithm() : index_(0), required_vertex_(0) {} - - // 'out' is set to the result if there is one, otherwise it's untouched. - void Execute(Vertex::Index vertex, - Graph* graph, - std::vector<Vertex::Index>* out); - - private: - void Tarjan(Vertex::Index vertex, Graph* graph); - - Vertex::Index index_; - Vertex::Index required_vertex_; - std::vector<Vertex::Index> stack_; - std::vector<std::vector<Vertex::Index>> components_; -}; - -} // namespace chromeos_update_engine - -#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_TARJAN_H_ diff --git a/payload_generator/tarjan_unittest.cc b/payload_generator/tarjan_unittest.cc deleted file mode 100644 index b271227f..00000000 --- a/payload_generator/tarjan_unittest.cc +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (C) 2010 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/tarjan.h" - -#include <string> -#include <utility> - -#include <base/logging.h> -#include <base/stl_util.h> -#include <gtest/gtest.h> - -#include "update_engine/payload_generator/graph_types.h" - -using std::make_pair; -using std::string; -using std::vector; - -namespace chromeos_update_engine { - -class TarjanAlgorithmTest : public ::testing::Test {}; - -TEST(TarjanAlgorithmTest, SimpleTest) { - const Vertex::Index n_a = 0; - const Vertex::Index n_b = 1; - const Vertex::Index n_c = 2; - const Vertex::Index n_d = 3; - const Vertex::Index n_e = 4; - const Vertex::Index n_f = 5; - const Vertex::Index n_g = 6; - const Vertex::Index n_h = 7; - const Graph::size_type kNodeCount = 8; - - Graph graph(kNodeCount); - - graph[n_a].out_edges.insert(make_pair(n_e, EdgeProperties())); - graph[n_a].out_edges.insert(make_pair(n_f, EdgeProperties())); - graph[n_b].out_edges.insert(make_pair(n_a, EdgeProperties())); - graph[n_c].out_edges.insert(make_pair(n_d, EdgeProperties())); - graph[n_d].out_edges.insert(make_pair(n_e, EdgeProperties())); - graph[n_d].out_edges.insert(make_pair(n_f, EdgeProperties())); - graph[n_e].out_edges.insert(make_pair(n_b, EdgeProperties())); - graph[n_e].out_edges.insert(make_pair(n_c, EdgeProperties())); - graph[n_e].out_edges.insert(make_pair(n_f, EdgeProperties())); - graph[n_f].out_edges.insert(make_pair(n_g, EdgeProperties())); - graph[n_g].out_edges.insert(make_pair(n_h, EdgeProperties())); - graph[n_h].out_edges.insert(make_pair(n_g, EdgeProperties())); - - TarjanAlgorithm tarjan; - - for (Vertex::Index i = n_a; i <= n_e; i++) { - vector<Vertex::Index> vertex_indexes; - tarjan.Execute(i, &graph, &vertex_indexes); - - EXPECT_EQ(5U, vertex_indexes.size()); - EXPECT_TRUE(base::ContainsValue(vertex_indexes, n_a)); - EXPECT_TRUE(base::ContainsValue(vertex_indexes, n_b)); - EXPECT_TRUE(base::ContainsValue(vertex_indexes, n_c)); - EXPECT_TRUE(base::ContainsValue(vertex_indexes, n_d)); - EXPECT_TRUE(base::ContainsValue(vertex_indexes, n_e)); - } - - { - vector<Vertex::Index> vertex_indexes; - tarjan.Execute(n_f, &graph, &vertex_indexes); - - EXPECT_EQ(1U, vertex_indexes.size()); - EXPECT_TRUE(base::ContainsValue(vertex_indexes, n_f)); - } - - for (Vertex::Index i = n_g; i <= n_h; i++) { - vector<Vertex::Index> vertex_indexes; - tarjan.Execute(i, &graph, &vertex_indexes); - - EXPECT_EQ(2U, vertex_indexes.size()); - EXPECT_TRUE(base::ContainsValue(vertex_indexes, n_g)); - EXPECT_TRUE(base::ContainsValue(vertex_indexes, n_h)); - } -} - -} // namespace chromeos_update_engine diff --git a/payload_generator/topological_sort.cc b/payload_generator/topological_sort.cc deleted file mode 100644 index 0abd7089..00000000 --- a/payload_generator/topological_sort.cc +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (C) 2010 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/topological_sort.h" - -#include <set> -#include <vector> - -#include <base/logging.h> - -using std::set; -using std::vector; - -namespace chromeos_update_engine { - -namespace { -void TopologicalSortVisit(const Graph& graph, - set<Vertex::Index>* visited_nodes, - vector<Vertex::Index>* nodes, - Vertex::Index node) { - if (visited_nodes->find(node) != visited_nodes->end()) - return; - - visited_nodes->insert(node); - // Visit all children. - for (Vertex::EdgeMap::const_iterator it = graph[node].out_edges.begin(); - it != graph[node].out_edges.end(); - ++it) { - TopologicalSortVisit(graph, visited_nodes, nodes, it->first); - } - // Visit this node. - nodes->push_back(node); -} -} // namespace - -void TopologicalSort(const Graph& graph, vector<Vertex::Index>* out) { - set<Vertex::Index> visited_nodes; - - for (Vertex::Index i = 0; i < graph.size(); i++) { - TopologicalSortVisit(graph, &visited_nodes, out, i); - } -} - -} // namespace chromeos_update_engine diff --git a/payload_generator/topological_sort.h b/payload_generator/topological_sort.h deleted file mode 100644 index 461cbe16..00000000 --- a/payload_generator/topological_sort.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (C) 2010 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_TOPOLOGICAL_SORT_H_ -#define UPDATE_ENGINE_PAYLOAD_GENERATOR_TOPOLOGICAL_SORT_H_ - -#include <vector> - -#include "update_engine/payload_generator/graph_types.h" - -namespace chromeos_update_engine { - -// Performs a topological sort on the directed graph 'graph' and stores -// the nodes, in order visited, in 'out'. -// For example, this graph: -// A ---> C ----. -// \ v -// `--> B --> D -// Might result in this in 'out': -// out[0] = D -// out[1] = B -// out[2] = C -// out[3] = A -// Note: results are undefined if there is a cycle in the graph. -void TopologicalSort(const Graph& graph, std::vector<Vertex::Index>* out); - -} // namespace chromeos_update_engine - -#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_TOPOLOGICAL_SORT_H_ diff --git a/payload_generator/topological_sort_unittest.cc b/payload_generator/topological_sort_unittest.cc deleted file mode 100644 index aa296d83..00000000 --- a/payload_generator/topological_sort_unittest.cc +++ /dev/null @@ -1,96 +0,0 @@ -// -// Copyright (C) 2010 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/topological_sort.h" - -#include <utility> -#include <vector> - -#include <gtest/gtest.h> - -#include "update_engine/payload_generator/graph_types.h" - -using std::make_pair; -using std::vector; - -namespace chromeos_update_engine { - -class TopologicalSortTest : public ::testing::Test {}; - -namespace { -// Returns true if the value is found in vect. If found, the index is stored -// in out_index if out_index is not null. -template <typename T> -bool IndexOf(const vector<T>& vect, - const T& value, - typename vector<T>::size_type* out_index) { - for (typename vector<T>::size_type i = 0; i < vect.size(); i++) { - if (vect[i] == value) { - if (out_index) { - *out_index = i; - } - return true; - } - } - return false; -} -} // namespace - -TEST(TopologicalSortTest, SimpleTest) { - int counter = 0; - const Vertex::Index n_a = counter++; - const Vertex::Index n_b = counter++; - const Vertex::Index n_c = counter++; - const Vertex::Index n_d = counter++; - const Vertex::Index n_e = counter++; - const Vertex::Index n_f = counter++; - const Vertex::Index n_g = counter++; - const Vertex::Index n_h = counter++; - const Vertex::Index n_i = counter++; - const Vertex::Index n_j = counter++; - const Graph::size_type kNodeCount = counter++; - - Graph graph(kNodeCount); - - graph[n_i].out_edges.insert(make_pair(n_j, EdgeProperties())); - graph[n_i].out_edges.insert(make_pair(n_c, EdgeProperties())); - graph[n_i].out_edges.insert(make_pair(n_e, EdgeProperties())); - graph[n_i].out_edges.insert(make_pair(n_h, EdgeProperties())); - graph[n_c].out_edges.insert(make_pair(n_b, EdgeProperties())); - graph[n_b].out_edges.insert(make_pair(n_a, EdgeProperties())); - graph[n_e].out_edges.insert(make_pair(n_d, EdgeProperties())); - graph[n_e].out_edges.insert(make_pair(n_g, EdgeProperties())); - graph[n_g].out_edges.insert(make_pair(n_d, EdgeProperties())); - graph[n_g].out_edges.insert(make_pair(n_f, EdgeProperties())); - graph[n_d].out_edges.insert(make_pair(n_a, EdgeProperties())); - - vector<Vertex::Index> sorted; - TopologicalSort(graph, &sorted); - - for (Vertex::Index i = 0; i < graph.size(); i++) { - vector<Vertex::Index>::size_type src_index = 0; - EXPECT_TRUE(IndexOf(sorted, i, &src_index)); - for (Vertex::EdgeMap::const_iterator it = graph[i].out_edges.begin(); - it != graph[i].out_edges.end(); - ++it) { - vector<Vertex::Index>::size_type dst_index = 0; - EXPECT_TRUE(IndexOf(sorted, it->first, &dst_index)); - EXPECT_LT(dst_index, src_index); - } - } -} - -} // namespace chromeos_update_engine diff --git a/payload_generator/zip_unittest.cc b/payload_generator/zip_unittest.cc index e357b156..10e899b0 100644 --- a/payload_generator/zip_unittest.cc +++ b/payload_generator/zip_unittest.cc @@ -33,7 +33,6 @@ using chromeos_update_engine::test_utils::kRandomString; using google::protobuf::RepeatedPtrField; using std::string; -using std::vector; namespace chromeos_update_engine { @@ -50,8 +49,7 @@ class MemoryExtentWriter : public ExtentWriter { } ~MemoryExtentWriter() override = default; - bool Init(FileDescriptorPtr fd, - const RepeatedPtrField<Extent>& extents, + bool Init(const RepeatedPtrField<Extent>& extents, uint32_t block_size) override { return true; } @@ -72,7 +70,7 @@ bool DecompressWithWriter(const brillo::Blob& in, brillo::Blob* out) { std::unique_ptr<ExtentWriter> writer( new W(std::make_unique<MemoryExtentWriter>(out))); // Init() parameters are ignored by the testing MemoryExtentWriter. - bool ok = writer->Init(nullptr, {}, 1); + bool ok = writer->Init({}, 1); ok = writer->Write(in.data(), in.size()) && ok; return ok; } diff --git a/power_manager_android.h b/power_manager_android.h deleted file mode 100644 index 86399abc..00000000 --- a/power_manager_android.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (C) 2016 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_POWER_MANAGER_ANDROID_H_ -#define UPDATE_ENGINE_POWER_MANAGER_ANDROID_H_ - -#include <base/macros.h> - -#include "update_engine/power_manager_interface.h" - -namespace chromeos_update_engine { - -class PowerManagerAndroid : public PowerManagerInterface { - public: - PowerManagerAndroid() = default; - ~PowerManagerAndroid() override = default; - - // PowerManagerInterface overrides. - bool RequestReboot() override; - - private: - DISALLOW_COPY_AND_ASSIGN(PowerManagerAndroid); -}; - -} // namespace chromeos_update_engine - -#endif // UPDATE_ENGINE_POWER_MANAGER_ANDROID_H_ diff --git a/protobuflint.py b/protobuflint.py new file mode 100755 index 00000000..02ccdbe5 --- /dev/null +++ b/protobuflint.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# +# Copyright (C) 2021 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""CLI script for linting .proto files inside update_engine.""" + +import sys +import re +import subprocess + +def check_proto_file(commit_hash, filename): + """Check if |filename| is consistnet with our protobuf guidelines + + Args: + commit_hash: Hash of the git commit to look + filename: A filesystem path to a .proto file + Returns: + True if this file passes linting check, False otherwise + """ + output = subprocess.check_output( + ["git", "diff", commit_hash+"~", commit_hash, "--", filename]) + output = output.decode() + p = re.compile(r"^[+]?\s*required .*$", re.M) + m = p.search(output) + if m: + print("File", filename, + "contains 'required' keyword. Usage of required", + "is strongly discouraged in protobuf", m.group()) + return False + return True + +def main(): + if len(sys.argv) < 2: + print("Usage:", sys.argv[0], "commit_hash", "<file1>", "<file2>", "...") + sys.exit(1) + commit_hash = sys.argv[1] + for filename in sys.argv[2:]: + if filename.endswith(".proto"): + if not check_proto_file(commit_hash, filename): + sys.exit(1) + +if __name__ == "__main__": + main() @@ -24,7 +24,7 @@ # Profiled execution. profile=no -# Add files or directories to the blacklist. They should be base names, not +# Add files or directories to the ignorelist. They should be base names, not # paths. ignore=CVS,.svn,.git,update_metadata_pb2.py diff --git a/sample_images/generate_images.sh b/sample_images/generate_images.sh index 8478682e..81a3296d 100755 --- a/sample_images/generate_images.sh +++ b/sample_images/generate_images.sh @@ -186,14 +186,27 @@ echo global_progress 1.0 >&3 exit 0 EOF + # An unlabeled postinstall bash program. + sudo tee "${mntdir}"/bin/self_check_default_context >/dev/null <<EOF +#!/etc/../bin/sh +echo "This is my context:" +ls -lZ "\$0" +ls -lZ "\$0" | grep -F ' u:object_r:postinstall_file:s0 ' || exit 5 +exit 0 +EOF + # A postinstall bash program. sudo tee "${mntdir}"/bin/self_check_context >/dev/null <<EOF #!/etc/../bin/sh echo "This is my context:" -ls -lZ "\$0" | grep -F ' u:object_r:postinstall_file:s0 ' || exit 5 +ls -lZ "\$0" +ls -lZ "\$0" | grep -F ' u:object_r:postinstall_exec:s0 ' || exit 5 exit 0 EOF + # Give the test function the context we expect the postinstall-executable to have. + sudo setfattr -n security.selinux -v u:object_r:postinstall_exec:s0 "${mntdir}"/bin/self_check_context + sudo tee "${mntdir}"/postinst >/dev/null <<EOF #!/etc/../bin/sh echo "postinst" @@ -270,6 +283,7 @@ main() { # Add squashfs sample images. generate_image disk_sqfs_empty sqfs empty $((1024 * 4096)) 4096 generate_image disk_sqfs_default sqfs default $((1024 * 4096)) 4096 + generate_image disk_sqfs_unittest sqfs unittest $((1024 * 4096)) 4096 # Generate the tarball and delete temporary images. echo "Packing tar file sample_images.tar.bz2" diff --git a/sample_images/generate_payloads.sh b/sample_images/generate_payloads.sh new file mode 100755 index 00000000..ee64229c --- /dev/null +++ b/sample_images/generate_payloads.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# +# 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. +# + +# This script generates some sample payloads from the images in +# sample_images.tar.bz2. and packages them in the sample_payloads.tar.xz file. +# The payloads are then used in paycheck_unittests.py. The file names +# must match the ones used in update_payload ebuild and paycheck_unittests.py. + +set -e + +TEMP_IMG_DIR=./sample_images +OLD_KERNEL="${TEMP_IMG_DIR}/disk_ext2_4k_empty.img" +OLD_ROOT="${TEMP_IMG_DIR}/disk_sqfs_empty.img" +NEW_KERNEL="${TEMP_IMG_DIR}/disk_ext2_4k.img" +NEW_ROOT="${TEMP_IMG_DIR}/disk_sqfs_default.img" + + +mkdir -p "${TEMP_IMG_DIR}" +tar -xvf sample_images.tar.bz2 -C "${TEMP_IMG_DIR}" + +echo "Generating full payload" +delta_generator --out_file=full_payload.bin \ + --partition_names=kernel:root \ + --new_partitions="${NEW_KERNEL}":"${NEW_ROOT}" + +echo "Generating delta payload" +delta_generator --out_file=delta_payload.bin \ + --partition_names=kernel:root \ + --new_partitions="${NEW_KERNEL}":"${NEW_ROOT}" \ + --old_partitions="${OLD_KERNEL}":"${OLD_ROOT}" --minor_version=6 + +echo "Creating sample_payloads.tar" +tar -cJf sample_payloads.tar.xz {delta,full}_payload.bin + +rm -rf "${TEMP_IMG_DIR}" {delta,full}_payload.bin + +echo "Done" diff --git a/sample_images/sample_images.tar.bz2 b/sample_images/sample_images.tar.bz2 Binary files differindex 62154822..7965d8b5 100644 --- a/sample_images/sample_images.tar.bz2 +++ b/sample_images/sample_images.tar.bz2 diff --git a/sample_images/sample_payloads.tar.xz b/sample_images/sample_payloads.tar.xz Binary files differnew file mode 100644 index 00000000..eb589ba6 --- /dev/null +++ b/sample_images/sample_payloads.tar.xz diff --git a/scripts/blockdiff.py b/scripts/blockdiff.py index 5793def7..95893cf4 100755 --- a/scripts/blockdiff.py +++ b/scripts/blockdiff.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/env python # # Copyright (C) 2013 The Android Open Source Project # @@ -17,6 +17,7 @@ """Block diff utility.""" +from __future__ import absolute_import from __future__ import print_function # pylint: disable=import-error @@ -46,7 +47,7 @@ def BlockDiff(block_size, file1, file2, name1, name2, max_length=-1): """ if max_length < 0: - max_length = sys.maxint + max_length = sys.maxsize diff_list = [] num_blocks = extent_start = extent_length = 0 while max_length or extent_length: diff --git a/scripts/brillo_update_payload b/scripts/brillo_update_payload index d9c18ff6..746cefba 100755 --- a/scripts/brillo_update_payload +++ b/scripts/brillo_update_payload @@ -89,12 +89,14 @@ die() { exit 1 } -# Loads shflags. We first look at the default install location; then look for -# crosutils (chroot); finally check our own directory. +# Loads shflags. We first look at the default install location; then our own +# directory; finally the parent directory. load_shflags() { local my_dir="$(dirname "$(readlink -f "$0")")" local path - for path in /usr/share/misc "${my_dir}"/lib/shflags; do + for path in /usr/share/misc \ + "${my_dir}"/lib/shflags \ + "${my_dir}"/../lib/shflags; do if [[ -r "${path}/shflags" ]]; then . "${path}/shflags" || die "Could not load ${path}/shflags." return @@ -186,9 +188,22 @@ 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 disable_verity_computation "" \ + "Optional: Disables the on device verity 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." + DEFINE_string full_boot "" "Will include full boot image" + DEFINE_string disable_vabc "" \ + "Optional: Disables Virtual AB Compression when installing the OTA" fi if [[ "${COMMAND}" == "hash" || "${COMMAND}" == "sign" ]]; then DEFINE_string unsigned_payload "" "Path to the input unsigned payload." @@ -271,6 +286,9 @@ POSTINSTALL_CONFIG_FILE="" # Path to the dynamic partition info file in target image if exists. DYNAMIC_PARTITION_INFO_FILE="" +# Path to the META/apex_info.pb found in target build +APEX_INFO_FILE="" + # read_option_int <file.txt> <option_key> [default_value] # # Reads the unsigned integer value associated with |option_key| in a key=value @@ -554,6 +572,13 @@ Disabling deltas for this source version." "${dynamic_partitions_info}" DYNAMIC_PARTITION_INFO_FILE="${dynamic_partitions_info}" fi + local apex_info=$(create_tempfile "apex_info.XXXXXX") + CLEANUP_FILES+=("${apex_info}") + if unzip -l "${image}" "META/apex_info.pb" > /dev/null; then + extract_file "${image}" "META/apex_info.pb" \ + "${apex_info}" + APEX_INFO_FILE="${apex_info}" + fi fi local part @@ -642,7 +667,12 @@ cmd_generate() { fi partition_names+="${part}" new_partitions+="${DST_PARTITIONS[${part}]}" - old_partitions+="${SRC_PARTITIONS[${part}]:-}" + if [ "${FLAGS_full_boot}" == "true" ] && [ "${part}" == "boot" ]; then + # Skip boot partition. + old_partitions+="" + else + old_partitions+="${SRC_PARTITIONS[${part}]:-}" + fi new_mapfiles+="${DST_PARTITIONS_MAP[${part}]:-}" old_mapfiles+="${SRC_PARTITIONS_MAP[${part}]:-}" done @@ -654,19 +684,40 @@ 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}" ) - fi if [[ -n "${FLAGS_disable_fec_computation}" ]]; then GENERATOR_ARGS+=( --disable_fec_computation="${FLAGS_disable_fec_computation}" ) fi + if [[ -n "${FLAGS_disable_verity_computation}" ]]; then + GENERATOR_ARGS+=( + --disable_verity_computation="${FLAGS_disable_verity_computation}" ) + fi + fi + + if [[ -n "${FLAGS_disable_vabc}" ]]; then + GENERATOR_ARGS+=( + --disable_vabc="${FLAGS_disable_vabc}" ) + 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 @@ -681,6 +732,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}" @@ -692,6 +747,11 @@ cmd_generate() { --dynamic_partition_info_file="${DYNAMIC_PARTITION_INFO_FILE}" ) fi + if [[ -n "{APEX_INFO_FILE}" ]]; then + GENERATOR_ARGS+=( + --apex_info_file="${APEX_INFO_FILE}" + ) + fi echo "Running delta_generator with args: ${GENERATOR_ARGS[@]}" "${GENERATOR}" "${GENERATOR_ARGS[@]}" @@ -881,8 +941,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/cow_converter.py b/scripts/cow_converter.py new file mode 100644 index 00000000..14e016cb --- /dev/null +++ b/scripts/cow_converter.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Command-line tool for converting OTA payloads to VABC style COW images.""" + +import os +import sys +import tempfile +import zipfile +import subprocess + + +def IsSparseImage(filepath): + """Determine if an image is a sparse image + Args: + filepath: str, a path to an .img file + + Returns: + return true iff the filepath is a sparse image. + + """ + with open(filepath, 'rb') as fp: + # Magic for android sparse image format + # https://source.android.com/devices/bootloader/images + return fp.read(4) == b'\x3A\xFF\x26\xED' + + +def ConvertCOW(ota_path, target_file_path, tmp_dir, output_dir): + """Convert ota payload to COW IMAGE + Args: + ota_path: str, path to ota.zip + target_file_path: str, path to target_file.zip, + must be the target build for OTA. + tmp_dir: A temp dir as scratch space + output_dir: A directory where all converted COW images will be written. + """ + with zipfile.ZipFile(ota_path) as ota_zip: + payload_path = ota_zip.extract("payload.bin", output_dir) + with zipfile.ZipFile(target_file_path) as zfp: + for fileinfo in zfp.infolist(): + img_name = os.path.basename(fileinfo.filename) + if not fileinfo.filename.endswith(".img"): + continue + if fileinfo.filename.startswith("IMAGES/") or \ + fileinfo.filename.startswith("RADIO/"): + img_path = zfp.extract(fileinfo, tmp_dir) + target_img_path = os.path.join(output_dir, img_name) + if IsSparseImage(img_path): + subprocess.check_call(["simg2img", img_path, target_img_path]) + else: + os.rename(img_path, target_img_path) + print("Extracted", fileinfo.filename, "size:", fileinfo.file_size) + + subprocess.call(["cow_converter", payload_path, + output_dir]) + + +def main(): + if len(sys.argv) != 4: + print( + "Usage:", sys.argv[0], "<your_ota.zip> <target_file.zip> <output dir>") + return 1 + ota_path = sys.argv[1] + target_file_path = sys.argv[2] + output_dir = sys.argv[3] + os.makedirs(output_dir, exist_ok=True) + with tempfile.TemporaryDirectory() as tmp_dir: + ConvertCOW(ota_path, target_file_path, tmp_dir, output_dir) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/ota_stress_test.py b/scripts/ota_stress_test.py new file mode 100644 index 00000000..55aa4b1e --- /dev/null +++ b/scripts/ota_stress_test.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Repeatedly install an A/B update to an Android device over adb.""" + +import argparse +import sys +from pathlib import Path +import subprocess +import signal + + +def CleanupLoopDevices(): + # b/184716804 clean up unused loop devices + subprocess.check_call(["adb", "shell", "su", "0", "losetup", '-D']) + + +def CancelOTA(): + subprocess.call(["adb", "shell", "su", "0", + "update_engine_client", "--cancel"]) + + +def PerformOTAThenPause(otafile: Path, update_device_script: Path): + python = sys.executable + ota_cmd = [python, str(update_device_script), str(otafile), + "--no-postinstall", "--no-slot-switch"] + p = subprocess.Popen(ota_cmd) + pid = p.pid + try: + ret = p.wait(10) + if ret is not None and ret != 0: + raise RuntimeError("OTA failed to apply") + if ret == 0: + print("OTA finished early? Surprise.") + return + except subprocess.TimeoutExpired: + pass + print(f"Killing {pid}") + subprocess.check_call(["pkill", "-INT", "-P", str(pid)]) + p.send_signal(signal.SIGINT) + p.wait() + + +def PerformTest(otafile: Path, resumes: int, timeout: int): + """Install an OTA to device, raising exceptions on failure + + Args: + otafile: Path to the ota.zip to install + + Return: + None if no error, if there's an error exception will be thrown + """ + assert otafile.exists() + print("Applying", otafile) + script_dir = Path(__file__).parent.absolute() + update_device_script = script_dir / "update_device.py" + assert update_device_script.exists() + print(update_device_script) + python = sys.executable + + for i in range(resumes): + print("Pause/Resume for the", i+1, "th time") + PerformOTAThenPause(otafile, update_device_script) + CancelOTA() + CleanupLoopDevices() + + ota_cmd = [python, str(update_device_script), + str(otafile), "--no-postinstall"] + print("Finishing OTA Update", ota_cmd) + output = subprocess.check_output( + ota_cmd, stderr=subprocess.STDOUT, timeout=timeout).decode() + print(output) + if "onPayloadApplicationComplete(ErrorCode::kSuccess" not in output: + raise RuntimeError("Failed to finish OTA") + subprocess.call( + ["adb", "shell", "su", "0", "update_engine_client", "--cancel"]) + subprocess.check_call( + ["adb", "shell", "su", "0", "update_engine_client", "--reset_status"]) + CleanupLoopDevices() + + +def main(): + parser = argparse.ArgumentParser( + description='Android A/B OTA stress test helper.') + parser.add_argument('otafile', metavar='PAYLOAD', type=Path, + help='the OTA package file (a .zip file) or raw payload \ + if device uses Omaha.') + parser.add_argument('-n', "--iterations", type=int, default=10, + metavar='ITERATIONS', + help='The number of iterations to run the stress test, or\ + -1 to keep running until CTRL+C') + parser.add_argument('-r', "--resumes", type=int, default=5, metavar='RESUMES', + help='The number of iterations to pause the update when \ + installing') + parser.add_argument('-t', "--timeout", type=int, default=60*60, + metavar='TIMEOUTS', + help='Timeout, in seconds, when waiting for OTA to \ + finish') + args = parser.parse_args() + print(args) + n = args.iterations + while n != 0: + PerformTest(args.otafile, args.resumes, args.timeout) + n -= 1 + + +if __name__ == "__main__": + main() diff --git a/scripts/paycheck.py b/scripts/paycheck.py index 9d617787..cb1713f0 100755 --- a/scripts/paycheck.py +++ b/scripts/paycheck.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/env python # # Copyright (C) 2013 The Android Open Source Project # @@ -17,6 +17,7 @@ """Command-line tool for checking and applying Chrome OS update payloads.""" +from __future__ import absolute_import from __future__ import print_function # pylint: disable=import-error @@ -26,18 +27,43 @@ import os import sys import tempfile -from update_payload import common +# pylint: disable=redefined-builtin +from six.moves import zip from update_payload import error + lib_dir = os.path.join(os.path.dirname(__file__), 'lib') if os.path.exists(lib_dir) and os.path.isdir(lib_dir): sys.path.insert(1, lib_dir) -import update_payload +import update_payload # pylint: disable=wrong-import-position _TYPE_FULL = 'full' _TYPE_DELTA = 'delta' +def CheckApplyPayload(args): + """Whether to check the result after applying the payload. + + Args: + args: Parsed command arguments (the return value of + ArgumentParser.parse_args). + + Returns: + Boolean value whether to check. + """ + return args.dst_part_paths is not None + +def ApplyPayload(args): + """Whether to apply the payload. + + Args: + args: Parsed command arguments (the return value of + ArgumentParser.parse_args). + + Returns: + Boolean value whether to apply the payload. + """ + return CheckApplyPayload(args) or args.out_dst_part_paths is not None def ParseArguments(argv): """Parse and validate command-line arguments. @@ -49,9 +75,9 @@ def ParseArguments(argv): Returns the arguments returned by the argument parser. """ parser = argparse.ArgumentParser( - description=('Applies a Chrome OS update PAYLOAD to src_kern and ' - 'src_root emitting dst_kern and dst_root, respectively. ' - 'src_kern and src_root are only needed for delta payloads. ' + description=('Applies a Chrome OS update PAYLOAD to src_part_paths' + 'emitting dst_part_paths, respectively. ' + 'src_part_paths are only needed for delta payloads. ' 'When no partitions are provided, verifies the payload ' 'integrity.'), epilog=('Note: a payload may verify correctly but fail to apply, and ' @@ -67,9 +93,6 @@ def ParseArguments(argv): check_args.add_argument('-c', '--check', action='store_true', default=False, help=('force payload integrity check (e.g. before ' 'applying)')) - check_args.add_argument('-D', '--describe', action='store_true', - default=False, - help='Print a friendly description of the payload.') check_args.add_argument('-r', '--report', metavar='FILE', help="dump payload report (`-' for stdout)") check_args.add_argument('-t', '--type', dest='assert_type', @@ -93,13 +116,6 @@ def ParseArguments(argv): check_args.add_argument('-s', '--metadata-size', metavar='NUM', default=0, help='the metadata size to verify with the one in' ' payload') - # TODO(tbrindus): deprecated in favour of --part_sizes - check_args.add_argument('-p', '--root-part-size', metavar='NUM', - default=0, type=int, - help='override rootfs partition size auto-inference') - check_args.add_argument('-P', '--kern-part-size', metavar='NUM', - default=0, type=int, - help='override kernel partition size auto-inference') check_args.add_argument('--part_sizes', metavar='NUM', nargs='+', type=int, help='override partition size auto-inference') @@ -113,21 +129,6 @@ def ParseArguments(argv): help='use the specified bspatch binary') apply_args.add_argument('--puffpatch-path', metavar='FILE', help='use the specified puffpatch binary') - # TODO(tbrindus): deprecated in favour of --dst_part_paths - apply_args.add_argument('--dst_kern', metavar='FILE', - help='destination kernel partition file') - apply_args.add_argument('--dst_root', metavar='FILE', - help='destination root partition file') - # TODO(tbrindus): deprecated in favour of --src_part_paths - apply_args.add_argument('--src_kern', metavar='FILE', - help='source kernel partition file') - apply_args.add_argument('--src_root', metavar='FILE', - help='source root partition file') - # TODO(tbrindus): deprecated in favour of --out_dst_part_paths - apply_args.add_argument('--out_dst_kern', metavar='FILE', - help='created destination kernel partition file') - apply_args.add_argument('--out_dst_root', metavar='FILE', - help='created destination root partition file') apply_args.add_argument('--src_part_paths', metavar='FILE', nargs='+', help='source partitition files') @@ -143,36 +144,28 @@ def ParseArguments(argv): # Parse command-line arguments. args = parser.parse_args(argv) - # TODO(tbrindus): temporary workaround to keep old-style flags from breaking - # without having to handle both types in our code. Remove after flag usage is - # removed from calling scripts. - args.part_names = args.part_names or [common.KERNEL, common.ROOTFS] - args.part_sizes = args.part_sizes or [args.kern_part_size, - args.root_part_size] - args.src_part_paths = args.src_part_paths or [args.src_kern, args.src_root] - args.dst_part_paths = args.dst_part_paths or [args.dst_kern, args.dst_root] - args.out_dst_part_paths = args.out_dst_part_paths or [args.out_dst_kern, - args.out_dst_root] - - # Make sure we don't have new dependencies on old flags by deleting them from - # the namespace here. - for old in ['kern_part_size', 'root_part_size', 'src_kern', 'src_root', - 'dst_kern', 'dst_root', 'out_dst_kern', 'out_dst_root']: - delattr(args, old) - # There are several options that imply --check. args.check = (args.check or args.report or args.assert_type or args.block_size or args.allow_unhashed or args.disabled_tests or args.meta_sig or args.key or - any(args.part_sizes) or args.metadata_size) + args.part_sizes is not None or args.metadata_size) + # Makes sure the following arguments have the same length as |part_names| if + # set. for arg in ['part_sizes', 'src_part_paths', 'dst_part_paths', 'out_dst_part_paths']: + if getattr(args, arg) is None: + # Parameter is not set. + continue if len(args.part_names) != len(getattr(args, arg, [])): parser.error('partitions in --%s do not match --part_names' % arg) - if all(args.dst_part_paths) or all(args.out_dst_part_paths): - if all(args.src_part_paths): + def _IsSrcPartPathsProvided(args): + return args.src_part_paths is not None + + # Makes sure parameters are coherent with payload type. + if ApplyPayload(args): + if _IsSrcPartPathsProvided(args): if args.assert_type == _TYPE_FULL: parser.error('%s payload does not accept source partition arguments' % _TYPE_FULL) @@ -208,15 +201,12 @@ def main(argv): # Parse and validate arguments. args = ParseArguments(argv[1:]) - with open(args.payload) as payload_file: + with open(args.payload, 'rb') as payload_file: payload = update_payload.Payload(payload_file) try: # Initialize payload. payload.Init() - if args.describe: - payload.Describe() - # Perform payload integrity checks. if args.check: report_file = None @@ -230,8 +220,9 @@ def main(argv): report_file = open(args.report, 'w') do_close_report_file = True - part_sizes = dict(zip(args.part_names, args.part_sizes)) - metadata_sig_file = args.meta_sig and open(args.meta_sig) + part_sizes = (args.part_sizes and + dict(zip(args.part_names, args.part_sizes))) + metadata_sig_file = args.meta_sig and open(args.meta_sig, 'rb') payload.Check( pubkey_file_name=args.key, metadata_sig_file=metadata_sig_file, @@ -249,7 +240,7 @@ def main(argv): report_file.close() # Apply payload. - if all(args.dst_part_paths) or all(args.out_dst_part_paths): + if ApplyPayload(args): dargs = {'bsdiff_in_place': not args.extract_bsdiff} if args.bspatch_path: dargs['bspatch_path'] = args.bspatch_path @@ -260,9 +251,9 @@ def main(argv): out_dst_parts = {} file_handles = [] - if all(args.out_dst_part_paths): + if args.out_dst_part_paths is not None: for name, path in zip(args.part_names, args.out_dst_part_paths): - handle = open(path, 'w+') + handle = open(path, 'wb+') file_handles.append(handle) out_dst_parts[name] = handle.name else: @@ -275,7 +266,7 @@ def main(argv): # If destination kernel and rootfs partitions are not given, then this # just becomes an apply operation with no check. - if all(args.dst_part_paths): + if CheckApplyPayload(args): # Prior to comparing, add the unused space past the filesystem # boundary in the new target partitions to become the same size as # the given partitions. This will truncate to larger size. @@ -293,7 +284,7 @@ def main(argv): # files are created as temp files and will be deleted upon close(). for handle in file_handles: handle.close() - except error.PayloadError, e: + except error.PayloadError as e: sys.stderr.write('Error: %s\n' % e) return 1 diff --git a/scripts/paycheck_unittest.py b/scripts/paycheck_unittest.py new file mode 100755 index 00000000..a90d269d --- /dev/null +++ b/scripts/paycheck_unittest.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# +# 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. +# + +"""Unit testing paycheck.py.""" + +# This test requires new (Y) and old (X) images, as well as a full payload +# from image Y and a delta payload from Y to X for each partition. +# Payloads are from sample_images/generate_payloads. +# +# The test performs the following: +# +# - It statically applies the full and delta payloads. +# +# - It applies full_payload to yield a new kernel (kern.part) and rootfs +# (root.part) and compares them to the new image partitions. +# +# - It applies delta_payload to the old image to yield a new kernel and rootfs +# and compares them to the new image partitions. +# +# Previously test_paycheck.sh. Run with update_payload ebuild. + +# Disable check for function names to avoid errors based on old code +# pylint: disable=invalid-name + +import filecmp +import os +import subprocess +import unittest + + +class PaycheckTest(unittest.TestCase): + """Test paycheck functions.""" + + def setUp(self): + self.tmpdir = os.getenv('T') + + self._full_payload = os.path.join(self.tmpdir, 'full_payload.bin') + self._delta_payload = os.path.join(self.tmpdir, 'delta_payload.bin') + + self._new_kernel = os.path.join(self.tmpdir, 'disk_ext2_4k.img') + self._new_root = os.path.join(self.tmpdir, 'disk_sqfs_default.img') + self._old_kernel = os.path.join(self.tmpdir, + 'disk_ext2_4k_empty.img') + self._old_root = os.path.join(self.tmpdir, 'disk_sqfs_empty.img') + + # Temp output files. + self._kernel_part = os.path.join(self.tmpdir, 'kern.part') + self._root_part = os.path.join(self.tmpdir, 'root.part') + + def checkPayload(self, type_arg, payload): + """Checks Payload.""" + self.assertEqual(0, subprocess.check_call(['./paycheck.py', '-t', + type_arg, payload])) + + def testFullPayload(self): + """Checks the full payload statically.""" + self.checkPayload('full', self._full_payload) + + def testDeltaPayload(self): + """Checks the delta payload statically.""" + self.checkPayload('delta', self._delta_payload) + + def testApplyFullPayload(self): + """Applies full payloads and compares results to new sample images.""" + self.assertEqual(0, subprocess.check_call(['./paycheck.py', + self._full_payload, + '--part_names', 'kernel', 'root', + '--out_dst_part_paths', + self._kernel_part, + self._root_part])) + + # Check if generated full image is equal to sample image. + self.assertTrue(filecmp.cmp(self._kernel_part, self._new_kernel)) + self.assertTrue(filecmp.cmp(self._root_part, self._new_root)) + + def testApplyDeltaPayload(self): + """Applies delta to old image and checks against new sample images.""" + self.assertEqual(0, subprocess.check_call(['./paycheck.py', + self._delta_payload, + '--part_names', 'kernel', 'root', + '--src_part_paths', + self._old_kernel, self._old_root, + '--out_dst_part_paths', + self._kernel_part, + self._root_part])) + + self.assertTrue(filecmp.cmp(self._kernel_part, self._new_kernel)) + self.assertTrue(filecmp.cmp(self._root_part, self._new_root)) + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/payload_info.py b/scripts/payload_info.py index 09a7cf78..8343d212 100755 --- a/scripts/payload_info.py +++ b/scripts/payload_info.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2015 The Android Open Source Project @@ -18,16 +18,17 @@ """payload_info: Show information about an update payload.""" +from __future__ import absolute_import from __future__ import print_function import argparse -import itertools import sys import textwrap +from six.moves import range import update_payload -MAJOR_PAYLOAD_VERSION_CHROMEOS = 1 + MAJOR_PAYLOAD_VERSION_BRILLO = 2 def DisplayValue(key, value): @@ -41,12 +42,12 @@ def DisplayValue(key, value): def DisplayHexData(data, indent=0): """Print out binary data as a hex values.""" for off in range(0, len(data), 16): - chunk = data[off:off + 16] + chunk = bytearray(data[off:off + 16]) print(' ' * indent + - ' '.join('%.2x' % ord(c) for c in chunk) + + ' '.join('%.2x' % c for c in chunk) + ' ' * (16 - len(chunk)) + ' | ' + - ''.join(c if 32 <= ord(c) < 127 else '.' for c in chunk)) + ''.join(chr(c) if 32 <= c < 127 else '.' for c in chunk)) class PayloadCommand(object): @@ -69,15 +70,16 @@ class PayloadCommand(object): def _DisplayManifest(self): """Show information from the payload manifest.""" manifest = self.payload.manifest - if self.payload.header.version == MAJOR_PAYLOAD_VERSION_BRILLO: - DisplayValue('Number of partitions', len(manifest.partitions)) - for partition in manifest.partitions: - DisplayValue(' Number of "%s" ops' % partition.partition_name, - len(partition.operations)) - else: - DisplayValue('Number of operations', len(manifest.install_operations)) - DisplayValue('Number of kernel ops', - len(manifest.kernel_install_operations)) + DisplayValue('Number of partitions', len(manifest.partitions)) + 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) + for partition in manifest.partitions: + DisplayValue(" COW Size for " + + partition.partition_name, partition.estimate_cow_size) DisplayValue('Block size', manifest.block_size) DisplayValue('Minor version', manifest.minor_version) @@ -131,8 +133,8 @@ class PayloadCommand(object): Args: name: The name you want displayed above the operation table. - operations: The install_operations object that you want to display - information about. + operations: The operations object that you want to display information + about. """ def _DisplayExtents(extents, name): """Show information about extents.""" @@ -149,7 +151,7 @@ class PayloadCommand(object): op_dict = update_payload.common.OpType.NAMES print('%s:' % name) - for op, op_count in itertools.izip(operations, itertools.count()): + for op_count, op in enumerate(operations): print(' %d: %s' % (op_count, op_dict[op.type])) if op.HasField('data_offset'): print(' Data offset: %s' % op.data_offset) @@ -170,14 +172,9 @@ class PayloadCommand(object): read_blocks = 0 written_blocks = 0 num_write_seeks = 0 - if self.payload.header.version == MAJOR_PAYLOAD_VERSION_BRILLO: - partitions_operations = [part.operations for part in manifest.partitions] - else: - partitions_operations = [manifest.install_operations, - manifest.kernel_install_operations] - for operations in partitions_operations: + for partition in manifest.partitions: last_ext = None - for curr_op in operations: + for curr_op in partition.operations: read_blocks += sum([ext.num_blocks for ext in curr_op.src_extents]) written_blocks += sum([ext.num_blocks for ext in curr_op.dst_extents]) for curr_ext in curr_op.dst_extents: @@ -187,15 +184,10 @@ class PayloadCommand(object): num_write_seeks += 1 last_ext = curr_ext - if manifest.minor_version == 1: - # Rootfs and kernel are written during the filesystem copy in version 1. - written_blocks += manifest.old_rootfs_info.size / manifest.block_size - written_blocks += manifest.old_kernel_info.size / manifest.block_size - # Old and new rootfs and kernel are read once during verification - read_blocks += manifest.old_rootfs_info.size / manifest.block_size - read_blocks += manifest.old_kernel_info.size / manifest.block_size - read_blocks += manifest.new_rootfs_info.size / manifest.block_size - read_blocks += manifest.new_kernel_info.size / manifest.block_size + # Old and new partitions are read once during verification. + read_blocks += partition.old_partition_info.size // manifest.block_size + read_blocks += partition.new_partition_info.size // manifest.block_size + stats = {'read_blocks': read_blocks, 'written_blocks': written_blocks, 'num_write_seeks': num_write_seeks} @@ -219,21 +211,15 @@ class PayloadCommand(object): self._DisplayStats(self.payload.manifest) if self.options.list_ops: print() - if self.payload.header.version == MAJOR_PAYLOAD_VERSION_BRILLO: - for partition in self.payload.manifest.partitions: - self._DisplayOps('%s install operations' % partition.partition_name, - partition.operations) - else: - self._DisplayOps('Install operations', - self.payload.manifest.install_operations) - self._DisplayOps('Kernel install operations', - self.payload.manifest.kernel_install_operations) + for partition in self.payload.manifest.partitions: + self._DisplayOps('%s install operations' % partition.partition_name, + partition.operations) def main(): parser = argparse.ArgumentParser( description='Show information about an update payload.') - parser.add_argument('payload_file', type=file, + parser.add_argument('payload_file', type=argparse.FileType('rb'), help='The update payload file.') parser.add_argument('--list_ops', default=False, action='store_true', help='List the install operations and their extents.') @@ -245,5 +231,6 @@ def main(): PayloadCommand(args).Run() + if __name__ == '__main__': sys.exit(main()) diff --git a/scripts/payload_info_unittest.py b/scripts/payload_info_unittest.py index a4ee9d50..07bb679d 100755 --- a/scripts/payload_info_unittest.py +++ b/scripts/payload_info_unittest.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/env python # # Copyright (C) 2015 The Android Open Source Project # @@ -17,24 +17,31 @@ """Unit testing payload_info.py.""" +# Disable check for function names to avoid errors based on old code +# pylint: disable-msg=invalid-name + +from __future__ import absolute_import from __future__ import print_function -import StringIO -import collections -import mock import sys import unittest +from contextlib import contextmanager + +from six.moves import StringIO + +import mock # pylint: disable=import-error + import payload_info import update_payload -from contextlib import contextmanager - from update_payload import update_metadata_pb2 + class FakePayloadError(Exception): """A generic error when using the FakePayload.""" + class FakeOption(object): """Fake options object for testing.""" @@ -42,11 +49,12 @@ class FakeOption(object): self.list_ops = False self.stats = False self.signatures = False - for key, val in kwargs.iteritems(): + for key, val in kwargs.items(): setattr(self, key, val) if not hasattr(self, 'payload_file'): self.payload_file = None + class FakeOp(object): """Fake manifest operation for testing.""" @@ -54,48 +62,57 @@ class FakeOp(object): self.src_extents = src_extents self.dst_extents = dst_extents self.type = op_type - for key, val in kwargs.iteritems(): + for key, val in kwargs.items(): setattr(self, key, val) def HasField(self, field): return hasattr(self, field) + +class FakeExtent(object): + """Fake Extent for testing.""" + def __init__(self, start_block, num_blocks): + self.start_block = start_block + self.num_blocks = num_blocks + + +class FakePartitionInfo(object): + """Fake PartitionInfo for testing.""" + def __init__(self, size): + self.size = size + + class FakePartition(object): """Fake PartitionUpdate field for testing.""" - def __init__(self, partition_name, operations): + def __init__(self, partition_name, operations, old_size, new_size): self.partition_name = partition_name self.operations = operations + self.old_partition_info = FakePartitionInfo(old_size) + self.new_partition_info = FakePartitionInfo(new_size) + class FakeManifest(object): """Fake manifest for testing.""" - def __init__(self, major_version): - FakeExtent = collections.namedtuple('FakeExtent', - ['start_block', 'num_blocks']) - self.install_operations = [FakeOp([], - [FakeExtent(1, 1), FakeExtent(2, 2)], - update_payload.common.OpType.REPLACE_BZ, - dst_length=3*4096, - data_offset=1, - data_length=1)] - self.kernel_install_operations = [FakeOp( - [FakeExtent(1, 1)], - [FakeExtent(x, x) for x in xrange(20)], - update_payload.common.OpType.SOURCE_COPY, - src_length=4096)] - if major_version == payload_info.MAJOR_PAYLOAD_VERSION_BRILLO: - self.partitions = [FakePartition('root', self.install_operations), - FakePartition('kernel', - self.kernel_install_operations)] - self.install_operations = self.kernel_install_operations = [] + def __init__(self): + self.partitions = [ + FakePartition(update_payload.common.ROOTFS, + [FakeOp([], [FakeExtent(1, 1), FakeExtent(2, 2)], + update_payload.common.OpType.REPLACE_BZ, + dst_length=3*4096, + data_offset=1, + data_length=1) + ], 1 * 4096, 3 * 4096), + FakePartition(update_payload.common.KERNEL, + [FakeOp([FakeExtent(1, 1)], + [FakeExtent(x, x) for x in range(20)], + update_payload.common.OpType.SOURCE_COPY, + src_length=4096) + ], 2 * 4096, 4 * 4096), + ] self.block_size = 4096 self.minor_version = 4 - FakePartInfo = collections.namedtuple('FakePartInfo', ['size']) - self.old_rootfs_info = FakePartInfo(1 * 4096) - self.old_kernel_info = FakePartInfo(2 * 4096) - self.new_rootfs_info = FakePartInfo(3 * 4096) - self.new_kernel_info = FakePartInfo(4 * 4096) self.signatures_offset = None self.signatures_size = None @@ -103,26 +120,27 @@ class FakeManifest(object): """Fake HasField method based on the python members.""" return hasattr(self, field_name) and getattr(self, field_name) is not None + class FakeHeader(object): """Fake payload header for testing.""" - def __init__(self, version, manifest_len, metadata_signature_len): - self.version = version + def __init__(self, manifest_len, metadata_signature_len): + self.version = payload_info.MAJOR_PAYLOAD_VERSION_BRILLO self.manifest_len = manifest_len self.metadata_signature_len = metadata_signature_len @property def size(self): - return (20 if self.version == payload_info.MAJOR_PAYLOAD_VERSION_CHROMEOS - else 24) + return 24 + class FakePayload(object): """Fake payload for testing.""" - def __init__(self, major_version): - self._header = FakeHeader(major_version, 222, 0) + def __init__(self): + self._header = FakeHeader(222, 0) self.header = None - self._manifest = FakeManifest(major_version) + self._manifest = FakeManifest() self.manifest = None self._blobs = {} @@ -152,7 +170,7 @@ class FakePayload(object): def _AddSignatureToProto(proto, **kwargs): """Add a new Signature element to the passed proto.""" new_signature = proto.signatures.add() - for key, val in kwargs.iteritems(): + for key, val in kwargs.items(): setattr(new_signature, key, val) def AddPayloadSignature(self, **kwargs): @@ -170,6 +188,7 @@ class FakePayload(object): self._header.metadata_signature_len = len(blob) self._blobs[-len(blob)] = blob + class PayloadCommandTest(unittest.TestCase): """Test class for our PayloadCommand class.""" @@ -178,7 +197,7 @@ class PayloadCommandTest(unittest.TestCase): """A tool for capturing the sys.stdout""" stdout = sys.stdout try: - sys.stdout = StringIO.StringIO() + sys.stdout = StringIO() yield sys.stdout finally: sys.stdout = stdout @@ -192,52 +211,25 @@ class PayloadCommandTest(unittest.TestCase): with mock.patch.object(update_payload, 'Payload', return_value=payload), \ self.OutputCapturer() as output: payload_cmd.Run() - self.assertEquals(output.getvalue(), expected_out) + self.assertEqual(output.getvalue(), expected_out) def testDisplayValue(self): """Verify that DisplayValue prints what we expect.""" with self.OutputCapturer() as output: payload_info.DisplayValue('key', 'value') - self.assertEquals(output.getvalue(), 'key: value\n') + self.assertEqual(output.getvalue(), 'key: value\n') def testRun(self): """Verify that Run parses and displays the payload like we expect.""" payload_cmd = payload_info.PayloadCommand(FakeOption(action='show')) - payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_CHROMEOS) - expected_out = """Payload version: 1 -Manifest length: 222 -Number of operations: 1 -Number of kernel ops: 1 -Block size: 4096 -Minor version: 4 -""" - self.TestCommand(payload_cmd, payload, expected_out) - - def testListOpsOnVersion1(self): - """Verify that the --list_ops option gives the correct output.""" - payload_cmd = payload_info.PayloadCommand( - FakeOption(list_ops=True, action='show')) - payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_CHROMEOS) - expected_out = """Payload version: 1 + payload = FakePayload() + expected_out = """Payload version: 2 Manifest length: 222 -Number of operations: 1 -Number of kernel ops: 1 +Number of partitions: 2 + Number of "root" ops: 1 + Number of "kernel" ops: 1 Block size: 4096 Minor version: 4 - -Install operations: - 0: REPLACE_BZ - Data offset: 1 - Data length: 1 - Destination: 2 extents (3 blocks) - (1,1) (2,2) -Kernel install operations: - 0: SOURCE_COPY - Source: 1 extent (1 block) - (1,1) - Destination: 20 extents (190 blocks) - (0,0) (1,1) (2,2) (3,3) (4,4) (5,5) (6,6) (7,7) (8,8) (9,9) (10,10) - (11,11) (12,12) (13,13) (14,14) (15,15) (16,16) (17,17) (18,18) (19,19) """ self.TestCommand(payload_cmd, payload, expected_out) @@ -245,7 +237,7 @@ Kernel install operations: """Verify that the --list_ops option gives the correct output.""" payload_cmd = payload_info.PayloadCommand( FakeOption(list_ops=True, action='show')) - payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_BRILLO) + payload = FakePayload() expected_out = """Payload version: 2 Manifest length: 222 Number of partitions: 2 @@ -270,28 +262,11 @@ kernel install operations: """ self.TestCommand(payload_cmd, payload, expected_out) - def testStatsOnVersion1(self): - """Verify that the --stats option works correctly.""" - payload_cmd = payload_info.PayloadCommand( - FakeOption(stats=True, action='show')) - payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_CHROMEOS) - expected_out = """Payload version: 1 -Manifest length: 222 -Number of operations: 1 -Number of kernel ops: 1 -Block size: 4096 -Minor version: 4 -Blocks read: 11 -Blocks written: 193 -Seeks when writing: 18 -""" - self.TestCommand(payload_cmd, payload, expected_out) - def testStatsOnVersion2(self): """Verify that the --stats option works correctly on version 2.""" payload_cmd = payload_info.PayloadCommand( FakeOption(stats=True, action='show')) - payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_BRILLO) + payload = FakePayload() expected_out = """Payload version: 2 Manifest length: 222 Number of partitions: 2 @@ -309,11 +284,12 @@ Seeks when writing: 18 """Verify that the --signatures option works with unsigned payloads.""" payload_cmd = payload_info.PayloadCommand( FakeOption(action='show', signatures=True)) - payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_CHROMEOS) - expected_out = """Payload version: 1 + payload = FakePayload() + expected_out = """Payload version: 2 Manifest length: 222 -Number of operations: 1 -Number of kernel ops: 1 +Number of partitions: 2 + Number of "root" ops: 1 + Number of "kernel" ops: 1 Block size: 4096 Minor version: 4 No metadata signatures stored in the payload @@ -325,11 +301,11 @@ No payload signatures stored in the payload """Verify that the --signatures option shows the present signatures.""" payload_cmd = payload_info.PayloadCommand( FakeOption(action='show', signatures=True)) - payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_BRILLO) + payload = FakePayload() payload.AddPayloadSignature(version=1, - data='12345678abcdefgh\x00\x01\x02\x03') - payload.AddPayloadSignature(data='I am a signature so access is yes.') - payload.AddMetadataSignature(data='\x00\x0a\x0c') + data=b'12345678abcdefgh\x00\x01\x02\x03') + payload.AddPayloadSignature(data=b'I am a signature so access is yes.') + payload.AddMetadataSignature(data=b'\x00\x0a\x0c') expected_out = """Payload version: 2 Manifest length: 222 Number of partitions: 2 @@ -353,5 +329,6 @@ Payload signatures: (2 entries) """ self.TestCommand(payload_cmd, payload, expected_out) + if __name__ == '__main__': unittest.main() diff --git a/scripts/run_unittests b/scripts/run_unittests index 0d301bad..db5ed73d 100755 --- a/scripts/run_unittests +++ b/scripts/run_unittests @@ -26,5 +26,6 @@ for unittest_script in update_payload/*_unittest.py; do done ./payload_info_unittest.py +./paycheck_unittest.py exit 0 diff --git a/scripts/simulate_ota.py b/scripts/simulate_ota.py new file mode 100644 index 00000000..63499799 --- /dev/null +++ b/scripts/simulate_ota.py @@ -0,0 +1,147 @@ +# +# 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. +# + +"""Tools for running host side simulation of an OTA update.""" + + +from __future__ import print_function + +import argparse +import filecmp +import os +import shutil +import subprocess +import sys +import tempfile +import zipfile + +import update_payload + + +def extract_file(zip_file_path, entry_name, target_file_path): + """Extract a file from zip archive into |target_file_path|""" + with open(target_file_path, 'wb') as out_fp: + if isinstance(zip_file_path, zipfile.ZipFile): + with zip_file_path.open(entry_name) as fp: + shutil.copyfileobj(fp, out_fp) + elif os.path.isdir(zip_file_path): + with open(os.path.join(zip_file_path, entry_name), "rb") as fp: + shutil.copyfileobj(fp, out_fp) + +def is_sparse_image(filepath): + with open(filepath, 'rb') as fp: + # Magic for android sparse image format + # https://source.android.com/devices/bootloader/images + return fp.read(4) == b'\x3A\xFF\x26\xED' + +def extract_img(zip_archive, img_name, output_path): + entry_name = "IMAGES/" + img_name + ".img" + extract_file(zip_archive, entry_name, output_path) + if is_sparse_image(output_path): + raw_img_path = output_path + ".raw" + subprocess.check_output(["simg2img", output_path, raw_img_path]) + os.rename(raw_img_path, output_path) + +def run_ota(source, target, payload_path, tempdir): + """Run an OTA on host side""" + payload = update_payload.Payload(payload_path) + payload.Init() + if zipfile.is_zipfile(source): + source = zipfile.ZipFile(source) + if zipfile.is_zipfile(target): + target = zipfile.ZipFile(target) + + old_partitions = [] + new_partitions = [] + expected_new_partitions = [] + for part in payload.manifest.partitions: + name = part.partition_name + old_image = os.path.join(tempdir, "source_" + name + ".img") + new_image = os.path.join(tempdir, "target_" + name + ".img") + print("Extracting source image for", name) + extract_img(source, name, old_image) + print("Extracting target image for", name) + extract_img(target, name, new_image) + + old_partitions.append(old_image) + scratch_image_name = new_image + ".actual" + new_partitions.append(scratch_image_name) + with open(scratch_image_name, "wb") as fp: + fp.truncate(part.new_partition_info.size) + expected_new_partitions.append(new_image) + + delta_generator_args = ["delta_generator", "--in_file=" + payload_path] + partition_names = [ + part.partition_name for part in payload.manifest.partitions + ] + delta_generator_args.append("--partition_names=" + ":".join(partition_names)) + delta_generator_args.append("--old_partitions=" + ":".join(old_partitions)) + delta_generator_args.append("--new_partitions=" + ":".join(new_partitions)) + + subprocess.check_output(delta_generator_args) + + valid = True + for (expected_part, actual_part, part_name) in \ + zip(expected_new_partitions, new_partitions, partition_names): + if filecmp.cmp(expected_part, actual_part): + print("Partition `{}` is valid".format(part_name)) + else: + valid = False + print( + "Partition `{}` is INVALID expected image: {} actual image: {}" + .format(part_name, expected_part, actual_part)) + + if not valid and sys.stdout.isatty(): + input("Paused to investigate invalid partitions, press any key to exit.") + + +def main(): + parser = argparse.ArgumentParser( + description="Run host side simulation of OTA package") + parser.add_argument( + "--source", + help="Target file zip for the source build", + required=True, nargs=1) + parser.add_argument( + "--target", + help="Target file zip for the target build", + required=True, nargs=1) + parser.add_argument( + "payload", + help="payload.bin for the OTA package, or a zip of OTA package itself", + nargs=1) + args = parser.parse_args() + print(args) + + assert os.path.exists(args.source[0]), \ + "source target file must point to a valid zipfile or directory" + assert os.path.exists(args.target[0]), \ + "target path must point to a valid zipfile or directory" + + # pylint: disable=no-member + with tempfile.TemporaryDirectory() as tempdir: + payload_path = args.payload[0] + if zipfile.is_zipfile(payload_path): + with zipfile.ZipFile(payload_path, "r") as zfp: + payload_entry_name = 'payload.bin' + zfp.extract(payload_entry_name, tempdir) + payload_path = os.path.join(tempdir, payload_entry_name) + + run_ota(args.source[0], args.target[0], payload_path, tempdir) + + +if __name__ == '__main__': + main() diff --git a/scripts/test_paycheck.sh b/scripts/test_paycheck.sh deleted file mode 100755 index 239b9845..00000000 --- a/scripts/test_paycheck.sh +++ /dev/null @@ -1,169 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2013 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. -# - -# A test script for paycheck.py and the update_payload.py library. -# -# This script requires three payload files, along with a metadata signature for -# each, and a public key for verifying signatures. Payload include: -# -# - A full payload for release X (old_full_payload) -# -# - A full payload for release Y (new_full_payload), where Y > X -# -# - A delta payload from X to Y (delta_payload) -# -# The test performs the following: -# -# - It verifies each payload against its metadata signature, also asserting the -# payload type. Another artifact is a human-readable payload report, which -# is output to stdout to be inspected by the user. -# -# - It applies old_full_payload to yield old kernel (old_kern.part) and rootfs -# (old_root.part) partitions. -# -# - It applies delta_payload to old_{kern,root}.part to yield new kernel -# (new_delta_kern.part) and rootfs (new_delta_root.part) partitions. -# -# - It applies new_full_payload to yield reference new kernel -# (new_full_kern.part) and rootfs (new_full_root.part) partitions. -# -# - It compares new_{delta,full}_kern.part and new_{delta,full}_root.part to -# ensure that they are binary identical. -# -# If all steps have completed successfully we know with high certainty that -# paycheck.py (and hence update_payload.py) correctly parses both full and delta -# payloads, and applies them to yield the expected result. Finally, each -# paycheck.py execution is timed. - - -# Stop on errors, unset variables. -set -e -set -u - -# Temporary image files. -OLD_KERN_PART=old_kern.part -OLD_ROOT_PART=old_root.part -NEW_DELTA_KERN_PART=new_delta_kern.part -NEW_DELTA_ROOT_PART=new_delta_root.part -NEW_FULL_KERN_PART=new_full_kern.part -NEW_FULL_ROOT_PART=new_full_root.part -CROS_PARTS="kernel root" - - -log() { - echo "$@" >&2 -} - -die() { - log "$@" - exit 1 -} - -usage_and_exit() { - cat >&2 <<EOF -Usage: ${0##*/} old_full_payload delta_payload new_full_payload -EOF - exit -} - -check_payload() { - payload_file=$1 - payload_type=$2 - - time ${paycheck} -t ${payload_type} ${payload_file} -} - -apply_full_payload() { - payload_file=$1 - out_dst_kern_part="$2/$3" - out_dst_root_part="$2/$4" - - time ${paycheck} ${payload_file} \ - --part_names ${CROS_PARTS} \ - --out_dst_part_paths ${out_dst_kern_part} ${out_dst_root_part} -} - -apply_delta_payload() { - payload_file=$1 - out_dst_kern_part="$2/$3" - out_dst_root_part="$2/$4" - dst_kern_part="$2/$5" - dst_root_part="$2/$6" - src_kern_part="$2/$7" - src_root_part="$2/$8" - - time ${paycheck} ${payload_file} \ - --part_names ${CROS_PARTS} \ - --out_dst_part_paths ${out_dst_kern_part} ${out_dst_root_part} \ - --dst_part_paths ${dst_kern_part} ${dst_root_part} \ - --src_part_paths ${src_kern_part} ${src_root_part} -} - -main() { - # Read command-line arguments. - if [ $# == 1 ] && [ "$1" == "-h" ]; then - usage_and_exit - elif [ $# != 3 ]; then - die "Error: unexpected number of arguments" - fi - old_full_payload="$1" - delta_payload="$2" - new_full_payload="$3" - - # Find paycheck.py - paycheck=${0%/*}/paycheck.py - if [ -z "${paycheck}" ] || [ ! -x ${paycheck} ]; then - die "cannot find ${paycheck} or file is not executable" - fi - - # Check the payloads statically. - log "Checking payloads..." - check_payload "${old_full_payload}" full - check_payload "${new_full_payload}" full - check_payload "${delta_payload}" delta - log "Done" - - # Apply full/delta payloads and verify results are identical. - tmpdir="$(mktemp -d --tmpdir test_paycheck.XXXXXXXX)" - log "Initiating application of payloads at $tmpdir" - - log "Applying old full payload..." - apply_full_payload "${old_full_payload}" "${tmpdir}" "${OLD_KERN_PART}" \ - "${OLD_ROOT_PART}" - log "Done" - - log "Applying new full payload..." - apply_full_payload "${new_full_payload}" "${tmpdir}" "${NEW_FULL_KERN_PART}" \ - "${NEW_FULL_ROOT_PART}" - log "Done" - - log "Applying delta payload to old partitions..." - apply_delta_payload "${delta_payload}" "${tmpdir}" "${NEW_DELTA_KERN_PART}" \ - "${NEW_DELTA_ROOT_PART}" "${NEW_FULL_KERN_PART}" \ - "${NEW_FULL_ROOT_PART}" "${OLD_KERN_PART}" "${OLD_ROOT_PART}" - log "Done" - - log "Comparing results of delta and new full updates..." - diff "${tmpdir}/${NEW_FULL_KERN_PART}" "${tmpdir}/${NEW_DELTA_KERN_PART}" - diff "${tmpdir}/${NEW_FULL_ROOT_PART}" "${tmpdir}/${NEW_DELTA_ROOT_PART}" - log "Done" - - log "Cleaning up" - rm -fr "${tmpdir}" -} - -main "$@" diff --git a/scripts/update_device.py b/scripts/update_device.py index 371a89a8..f672cda0 100755 --- a/scripts/update_device.py +++ b/scripts/update_device.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/env python3 # # Copyright (C) 2017 The Android Open Source Project # @@ -17,8 +17,10 @@ """Send an A/B update to an Android device over adb.""" +from __future__ import print_function +from __future__ import absolute_import + import argparse -import BaseHTTPServer import binascii import hashlib import logging @@ -27,10 +29,13 @@ import socket import subprocess import sys import struct +import tempfile import threading import xml.etree.ElementTree import zipfile +from six.moves import BaseHTTPServer + import update_payload.payload @@ -43,6 +48,7 @@ PAYLOAD_KEY_PATH = '/etc/update_engine/update-payload-key.pub.pem' # The port on the device that update_engine should connect to. DEVICE_PORT = 1234 + def CopyFileObjLength(fsrc, fdst, buffer_size=128 * 1024, copy_length=None): """Copy from a file object to another. @@ -99,7 +105,7 @@ class AndroidOTAPackage(object): if payload_info.compress_type != 0: logging.error( - "Expected layload to be uncompressed, got compression method %d", + "Expected payload 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, @@ -120,10 +126,10 @@ class AndroidOTAPackage(object): payload_header = fp.read(4) if payload_header != self.PAYLOAD_MAGIC_HEADER: logging.warning( - "Invalid header, expeted %s, got %s." + "Invalid header, expected %s, got %s." "Either the offset is not correct, or payload is corrupted", binascii.hexlify(self.PAYLOAD_MAGIC_HEADER), - payload_header) + binascii.hexlify(payload_header)) property_entry = (self.SECONDARY_OTA_PAYLOAD_PROPERTIES_TXT if secondary_payload else self.OTA_PAYLOAD_PROPERTIES_TXT) @@ -164,7 +170,6 @@ class UpdateHandler(BaseHTTPServer.BaseHTTPRequestHandler): start_range = file_size - int(e) return start_range, end_range - def do_GET(self): # pylint: disable=invalid-name """Reply with the requested payload file.""" if self.path != '/payload': @@ -207,7 +212,6 @@ class UpdateHandler(BaseHTTPServer.BaseHTTPRequestHandler): f.seek(serving_start + start_range) CopyFileObjLength(f, self.wfile, copy_length=end_range - start_range) - def do_POST(self): # pylint: disable=invalid-name """Reply with the omaha response xml.""" if self.path != '/update': @@ -303,6 +307,7 @@ class ServerThread(threading.Thread): logging.info('Server Terminated') def StopServer(self): + self._httpd.shutdown() self._httpd.socket.close() @@ -316,13 +321,13 @@ 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, secondary) headers = ota.properties - headers += 'USER_AGENT=Dalvik (something, something)\n' - headers += 'NETWORK_ID=0\n' - headers += extra_headers + headers += b'USER_AGENT=Dalvik (something, something)\n' + headers += b'NETWORK_ID=0\n' + headers += extra_headers.encode() return ['update_engine_client', '--update', '--follow', '--payload=%s' % payload_url, '--offset=%d' % ota.offset, - '--size=%d' % ota.size, '--headers="%s"' % headers] + '--size=%d' % ota.size, '--headers="%s"' % headers.decode()] def OmahaUpdateCommand(omaha_url): @@ -345,7 +350,7 @@ class AdbHost(object): if self._device_serial: self._command_prefix += ['-s', self._device_serial] - def adb(self, command): + def adb(self, command, timeout_seconds: float = None): """Run an ADB command like "adb push". Args: @@ -360,7 +365,7 @@ class AdbHost(object): command = self._command_prefix + command logging.info('Running: %s', ' '.join(str(x) for x in command)) p = subprocess.Popen(command, universal_newlines=True) - p.wait() + p.wait(timeout_seconds) return p.returncode def adb_output(self, command): @@ -380,6 +385,28 @@ class AdbHost(object): return subprocess.check_output(command, universal_newlines=True) +def PushMetadata(dut, otafile, metadata_path): + payload = update_payload.Payload(otafile) + payload.Init() + with tempfile.TemporaryDirectory() as tmpdir: + with zipfile.ZipFile(otafile, "r") as zfp: + extracted_path = os.path.join(tmpdir, "payload.bin") + with zfp.open("payload.bin") as payload_fp, \ + open(extracted_path, "wb") as output_fp: + # Only extract the first |data_offset| bytes from the payload. + # This is because allocateSpaceForPayload only needs to see + # the manifest, not the entire payload. + # Extracting the entire payload works, but is slow for full + # OTA. + output_fp.write(payload_fp.read(payload.data_offset)) + + return dut.adb([ + "push", + extracted_path, + metadata_path + ]) == 0 + + def main(): parser = argparse.ArgumentParser(description='Android A/B OTA helper.') parser.add_argument('otafile', metavar='PAYLOAD', type=str, @@ -399,6 +426,17 @@ def main(): help='Extra headers to pass to the device.') parser.add_argument('--secondary', action='store_true', help='Update with the secondary payload in the package.') + parser.add_argument('--no-slot-switch', action='store_true', + help='Do not perform slot switch after the update.') + parser.add_argument('--no-postinstall', action='store_true', + help='Do not execute postinstall scripts after the update.') + parser.add_argument('--allocate-only', action='store_true', + help='Allocate space for this OTA, instead of actually \ + applying the OTA.') + parser.add_argument('--verify-only', action='store_true', + help='Verify metadata then exit, instead of applying the OTA.') + parser.add_argument('--no-care-map', action='store_true', + help='Do not push care_map.pb to device.') args = parser.parse_args() logging.basicConfig( level=logging.WARNING if args.no_verbose else logging.INFO) @@ -416,6 +454,40 @@ def main(): help_cmd = ['shell', 'su', '0', 'update_engine_client', '--help'] use_omaha = 'omaha' in dut.adb_output(help_cmd) + metadata_path = "/data/ota_package/metadata" + if args.allocate_only: + if PushMetadata(dut, args.otafile, metadata_path): + dut.adb([ + "shell", "update_engine_client", "--allocate", + "--metadata={}".format(metadata_path)]) + # Return 0, as we are executing ADB commands here, no work needed after + # this point + return 0 + if args.verify_only: + if PushMetadata(dut, args.otafile, metadata_path): + dut.adb([ + "shell", "update_engine_client", "--verify", + "--metadata={}".format(metadata_path)]) + # Return 0, as we are executing ADB commands here, no work needed after + # this point + return 0 + + if args.no_slot_switch: + args.extra_headers += "\nSWITCH_SLOT_ON_REBOOT=0" + if args.no_postinstall: + args.extra_headers += "\nRUN_POST_INSTALL=0" + + with zipfile.ZipFile(args.otafile) as zfp: + CARE_MAP_ENTRY_NAME = "care_map.pb" + if CARE_MAP_ENTRY_NAME in zfp.namelist() and not args.no_care_map: + # Need root permission to push to /data + dut.adb(["root"]) + with tempfile.NamedTemporaryFile() as care_map_fp: + care_map_fp.write(zfp.read(CARE_MAP_ENTRY_NAME)) + care_map_fp.flush() + dut.adb(["push", care_map_fp.name, + "/data/ota_package/" + CARE_MAP_ENTRY_NAME]) + if args.file: # Update via pushing a file to /data. device_ota_file = os.path.join(OTA_PACKAGE_PATH, 'debug.zip') @@ -474,9 +546,10 @@ def main(): if server_thread: server_thread.StopServer() for cmd in finalize_cmds: - dut.adb(cmd) + dut.adb(cmd, 5) return 0 + if __name__ == '__main__': sys.exit(main()) diff --git a/scripts/update_payload/__init__.py b/scripts/update_payload/__init__.py index 8ee95e20..6e77678d 100644 --- a/scripts/update_payload/__init__.py +++ b/scripts/update_payload/__init__.py @@ -17,6 +17,8 @@ """Library for processing, verifying and applying Chrome OS update payloads.""" # Just raise the interface classes to the root namespace. +from __future__ import absolute_import + from update_payload.checker import CHECKS_TO_DISABLE from update_payload.error import PayloadError from update_payload.payload import Payload diff --git a/scripts/update_payload/applier.py b/scripts/update_payload/applier.py index 21d8e875..29ccb8e5 100644 --- a/scripts/update_payload/applier.py +++ b/scripts/update_payload/applier.py @@ -24,12 +24,12 @@ payload. The interface for invoking the applier is as follows: """ +from __future__ import absolute_import from __future__ import print_function import array import bz2 import hashlib -import itertools # Not everywhere we can have the lzma library so we ignore it if we didn't have # it because it is not going to be used. For example, 'cros flash' uses # devserver code which eventually loads this file, but the lzma library is not @@ -45,7 +45,6 @@ except ImportError: except ImportError: pass import os -import shutil import subprocess import sys import tempfile @@ -53,7 +52,6 @@ import tempfile from update_payload import common from update_payload.error import PayloadError - # # Helper functions. # @@ -72,7 +70,7 @@ def _VerifySha256(file_obj, expected_hash, name, length=-1): """ hasher = hashlib.sha256() block_length = 1024 * 1024 - max_length = length if length >= 0 else sys.maxint + max_length = length if length >= 0 else sys.maxsize while max_length > 0: read_length = min(max_length, block_length) @@ -108,20 +106,16 @@ def _ReadExtents(file_obj, extents, block_size, max_length=-1): Returns: A character array containing the concatenated read data. """ - data = array.array('c') + data = array.array('B') if max_length < 0: - max_length = sys.maxint + max_length = sys.maxsize for ex in extents: if max_length == 0: break read_length = min(max_length, ex.num_blocks * block_size) - # Fill with zeros or read from file, depending on the type of extent. - if ex.start_block == common.PSEUDO_EXTENT_MARKER: - data.extend(itertools.repeat('\0', read_length)) - else: - file_obj.seek(ex.start_block * block_size) - data.fromfile(file_obj, read_length) + file_obj.seek(ex.start_block * block_size) + data.fromfile(file_obj, read_length) max_length -= read_length @@ -149,12 +143,8 @@ def _WriteExtents(file_obj, data, extents, block_size, base_name): if not data_length: raise PayloadError('%s: more write extents than data' % ex_name) write_length = min(data_length, ex.num_blocks * block_size) - - # Only do actual writing if this is not a pseudo-extent. - if ex.start_block != common.PSEUDO_EXTENT_MARKER: - file_obj.seek(ex.start_block * block_size) - data_view = buffer(data, data_offset, write_length) - file_obj.write(data_view) + file_obj.seek(ex.start_block * block_size) + file_obj.write(data[data_offset:(data_offset + write_length)]) data_offset += write_length data_length -= write_length @@ -184,20 +174,17 @@ def _ExtentsToBspatchArg(extents, block_size, base_name, data_length=-1): arg = '' pad_off = pad_len = 0 if data_length < 0: - data_length = sys.maxint + data_length = sys.maxsize for ex, ex_name in common.ExtentIter(extents, base_name): if not data_length: raise PayloadError('%s: more extents than total data length' % ex_name) - is_pseudo = ex.start_block == common.PSEUDO_EXTENT_MARKER - start_byte = -1 if is_pseudo else ex.start_block * block_size + start_byte = ex.start_block * block_size num_bytes = ex.num_blocks * block_size if data_length < num_bytes: # We're only padding a real extent. - if not is_pseudo: - pad_off = start_byte + data_length - pad_len = num_bytes - data_length - + pad_off = start_byte + data_length + pad_len = num_bytes - data_length num_bytes = data_length arg += '%s%d:%d' % (arg and ',', start_byte, num_bytes) @@ -274,30 +261,28 @@ class PayloadApplier(object): num_blocks = ex.num_blocks count = num_blocks * block_size - # Make sure it's not a fake (signature) operation. - if start_block != common.PSEUDO_EXTENT_MARKER: - data_end = data_start + count + data_end = data_start + count - # Make sure we're not running past partition boundary. - if (start_block + num_blocks) * block_size > part_size: - raise PayloadError( - '%s: extent (%s) exceeds partition size (%d)' % - (ex_name, common.FormatExtent(ex, block_size), - part_size)) + # Make sure we're not running past partition boundary. + if (start_block + num_blocks) * block_size > part_size: + raise PayloadError( + '%s: extent (%s) exceeds partition size (%d)' % + (ex_name, common.FormatExtent(ex, block_size), + part_size)) - # Make sure that we have enough data to write. - if data_end >= data_length + block_size: - raise PayloadError( - '%s: more dst blocks than data (even with padding)') + # Make sure that we have enough data to write. + if data_end >= data_length + block_size: + raise PayloadError( + '%s: more dst blocks than data (even with padding)') - # Pad with zeros if necessary. - if data_end > data_length: - padding = data_end - data_length - out_data += '\0' * padding + # Pad with zeros if necessary. + if data_end > data_length: + padding = data_end - data_length + out_data += b'\0' * padding - self.payload.payload_file.seek(start_block * block_size) - part_file.seek(start_block * block_size) - part_file.write(out_data[data_start:data_end]) + self.payload.payload_file.seek(start_block * block_size) + part_file.seek(start_block * block_size) + part_file.write(out_data[data_start:data_end]) data_start += count @@ -306,30 +291,6 @@ class PayloadApplier(object): raise PayloadError('%s: wrote fewer bytes (%d) than expected (%d)' % (op_name, data_start, data_length)) - def _ApplyMoveOperation(self, op, op_name, part_file): - """Applies a MOVE operation. - - Note that this operation must read the whole block data from the input and - only then dump it, due to our in-place update semantics; otherwise, it - might clobber data midway through. - - Args: - op: the operation object - op_name: name string for error reporting - part_file: the partition file object - - Raises: - PayloadError if something goes wrong. - """ - block_size = self.block_size - - # Gather input raw data from src extents. - in_data = _ReadExtents(part_file, op.src_extents, block_size) - - # Dump extracted data to dst extents. - _WriteExtents(part_file, in_data, op.dst_extents, block_size, - '%s.dst_extents' % op_name) - def _ApplyZeroOperation(self, op, op_name, part_file): """Applies a ZERO operation. @@ -347,10 +308,8 @@ class PayloadApplier(object): # Iterate over the extents and write zero. # pylint: disable=unused-variable for ex, ex_name in common.ExtentIter(op.dst_extents, base_name): - # Only do actual writing if this is not a pseudo-extent. - if ex.start_block != common.PSEUDO_EXTENT_MARKER: - part_file.seek(ex.start_block * block_size) - part_file.write('\0' * (ex.num_blocks * block_size)) + part_file.seek(ex.start_block * block_size) + part_file.write(b'\0' * (ex.num_blocks * block_size)) def _ApplySourceCopyOperation(self, op, op_name, old_part_file, new_part_file): @@ -439,12 +398,19 @@ class PayloadApplier(object): # Diff from source partition. old_file_name = '/dev/fd/%d' % old_part_file.fileno() - if op.type in (common.OpType.BSDIFF, common.OpType.SOURCE_BSDIFF, - common.OpType.BROTLI_BSDIFF): + # In python3, file descriptors(fd) are not passed to child processes by + # default. To pass the fds to the child processes, we need to set the flag + # 'inheritable' in the fds and make the subprocess calls with the argument + # close_fds set to False. + if sys.version_info.major >= 3: + os.set_inheritable(new_part_file.fileno(), True) + os.set_inheritable(old_part_file.fileno(), True) + + if op.type in (common.OpType.SOURCE_BSDIFF, common.OpType.BROTLI_BSDIFF): # Invoke bspatch on partition file with extents args. bspatch_cmd = [self.bspatch_path, old_file_name, new_file_name, patch_file_name, in_extents_arg, out_extents_arg] - subprocess.check_call(bspatch_cmd) + subprocess.check_call(bspatch_cmd, close_fds=False) elif op.type == common.OpType.PUFFDIFF: # Invoke puffpatch on partition file with extents args. puffpatch_cmd = [self.puffpatch_path, @@ -454,14 +420,14 @@ class PayloadApplier(object): "--patch_file=%s" % patch_file_name, "--src_extents=%s" % in_extents_arg, "--dst_extents=%s" % out_extents_arg] - subprocess.check_call(puffpatch_cmd) + subprocess.check_call(puffpatch_cmd, close_fds=False) else: - raise PayloadError("Unknown operation %s", op.type) + raise PayloadError("Unknown operation %s" % op.type) # Pad with zeros past the total output length. if pad_len: new_part_file.seek(pad_off) - new_part_file.write('\0' * pad_len) + new_part_file.write(b'\0' * pad_len) else: # Gather input raw data and write to a temp file. input_part_file = old_part_file if old_part_file else new_part_file @@ -477,8 +443,7 @@ class PayloadApplier(object): with tempfile.NamedTemporaryFile(delete=False) as out_file: out_file_name = out_file.name - if op.type in (common.OpType.BSDIFF, common.OpType.SOURCE_BSDIFF, - common.OpType.BROTLI_BSDIFF): + if op.type in (common.OpType.SOURCE_BSDIFF, common.OpType.BROTLI_BSDIFF): # Invoke bspatch. bspatch_cmd = [self.bspatch_path, in_file_name, out_file_name, patch_file_name] @@ -492,7 +457,7 @@ class PayloadApplier(object): "--patch_file=%s" % patch_file_name] subprocess.check_call(puffpatch_cmd) else: - raise PayloadError("Unknown operation %s", op.type) + raise PayloadError("Unknown operation %s" % op.type) # Read output. with open(out_file_name, 'rb') as out_file: @@ -505,7 +470,7 @@ class PayloadApplier(object): # Write output back to partition, with padding. unaligned_out_len = len(out_data) % block_size if unaligned_out_len: - out_data += '\0' * (block_size - unaligned_out_len) + out_data += b'\0' * (block_size - unaligned_out_len) _WriteExtents(new_part_file, out_data, op.dst_extents, block_size, '%s.dst_extents' % op_name) @@ -520,10 +485,6 @@ class PayloadApplier(object): new_part_file, part_size): """Applies a sequence of update operations to a partition. - This assumes an in-place update semantics for MOVE and BSDIFF, namely all - reads are performed first, then the data is processed and written back to - the same file. - Args: operations: the sequence of operations base_name: the name of the operation sequence @@ -541,13 +502,8 @@ class PayloadApplier(object): if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ, common.OpType.REPLACE_XZ): self._ApplyReplaceOperation(op, op_name, data, new_part_file, part_size) - elif op.type == common.OpType.MOVE: - self._ApplyMoveOperation(op, op_name, new_part_file) elif op.type == common.OpType.ZERO: self._ApplyZeroOperation(op, op_name, new_part_file) - elif op.type == common.OpType.BSDIFF: - self._ApplyDiffOperation(op, op_name, data, new_part_file, - new_part_file) elif op.type == common.OpType.SOURCE_COPY: self._ApplySourceCopyOperation(op, op_name, old_part_file, new_part_file) @@ -583,15 +539,8 @@ class PayloadApplier(object): _VerifySha256(old_part_file, old_part_info.hash, 'old ' + part_name, length=old_part_info.size) new_part_file_mode = 'r+b' - if self.minor_version == common.INPLACE_MINOR_PAYLOAD_VERSION: - # Copy the src partition to the dst one; make sure we don't truncate it. - shutil.copyfile(old_part_file_name, new_part_file_name) - elif self.minor_version >= common.SOURCE_MINOR_PAYLOAD_VERSION: - # In minor version >= 2, we don't want to copy the partitions, so - # instead just make the new partition file. - open(new_part_file_name, 'w').close() - else: - raise PayloadError("Unknown minor version: %d" % self.minor_version) + open(new_part_file_name, 'w').close() + else: # We need to create/truncate the dst partition file. new_part_file_mode = 'w+b' @@ -639,20 +588,11 @@ class PayloadApplier(object): install_operations = [] manifest = self.payload.manifest - if self.payload.header.version == 1: - for real_name, proto_name in common.CROS_PARTITIONS: - new_part_info[real_name] = getattr(manifest, 'new_%s_info' % proto_name) - old_part_info[real_name] = getattr(manifest, 'old_%s_info' % proto_name) - - install_operations.append((common.ROOTFS, manifest.install_operations)) - install_operations.append((common.KERNEL, - manifest.kernel_install_operations)) - else: - for part in manifest.partitions: - name = part.partition_name - new_part_info[name] = part.new_partition_info - old_part_info[name] = part.old_partition_info - install_operations.append((name, part.operations)) + for part in manifest.partitions: + name = part.partition_name + new_part_info[name] = part.new_partition_info + old_part_info[name] = part.old_partition_info + install_operations.append((name, part.operations)) part_names = set(new_part_info.keys()) # Equivalently, old_part_info.keys() diff --git a/scripts/update_payload/checker.py b/scripts/update_payload/checker.py index 6d17fbe5..56a93708 100644 --- a/scripts/update_payload/checker.py +++ b/scripts/update_payload/checker.py @@ -24,6 +24,7 @@ follows: checker.Run(...) """ +from __future__ import absolute_import from __future__ import print_function import array @@ -34,22 +35,22 @@ import itertools import os import subprocess +# pylint: disable=redefined-builtin +from six.moves import range + from update_payload import common from update_payload import error from update_payload import format_utils from update_payload import histogram from update_payload import update_metadata_pb2 - # # Constants. # -_CHECK_DST_PSEUDO_EXTENTS = 'dst-pseudo-extents' _CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block' _CHECK_PAYLOAD_SIG = 'payload-sig' CHECKS_TO_DISABLE = ( - _CHECK_DST_PSEUDO_EXTENTS, _CHECK_MOVE_SAME_SRC_DST_BLOCK, _CHECK_PAYLOAD_SIG, ) @@ -66,15 +67,14 @@ _DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__), # Supported minor version map to payload types allowed to be using them. _SUPPORTED_MINOR_VERSIONS = { 0: (_TYPE_FULL,), - 1: (_TYPE_DELTA,), 2: (_TYPE_DELTA,), 3: (_TYPE_DELTA,), 4: (_TYPE_DELTA,), 5: (_TYPE_DELTA,), 6: (_TYPE_DELTA,), + 7: (_TYPE_DELTA,), } -_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024 # # Helper functions. @@ -323,8 +323,6 @@ class PayloadChecker(object): self.allow_unhashed = allow_unhashed # Disable specific tests. - self.check_dst_pseudo_extents = ( - _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests) self.check_move_same_src_dst_block = ( _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests) self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests @@ -609,7 +607,7 @@ class PayloadChecker(object): """ self.major_version = self.payload.header.version - part_sizes = collections.defaultdict(int, part_sizes) + part_sizes = part_sizes or collections.defaultdict(int) manifest = self.payload.manifest report.AddSection('manifest') @@ -628,35 +626,23 @@ class PayloadChecker(object): self._CheckPresentIff(self.sigs_offset, self.sigs_size, 'signatures_offset', 'signatures_size', 'manifest') - if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION: - for real_name, proto_name in common.CROS_PARTITIONS: - self.old_part_info[real_name] = self._CheckOptionalSubMsg( - manifest, 'old_%s_info' % proto_name, report) - self.new_part_info[real_name] = self._CheckMandatorySubMsg( - manifest, 'new_%s_info' % proto_name, report, 'manifest') - - # Check: old_kernel_info <==> old_rootfs_info. - self._CheckPresentIff(self.old_part_info[common.KERNEL].msg, - self.old_part_info[common.ROOTFS].msg, - 'old_kernel_info', 'old_rootfs_info', 'manifest') - else: - for part in manifest.partitions: - name = part.partition_name - self.old_part_info[name] = self._CheckOptionalSubMsg( - part, 'old_partition_info', report) - self.new_part_info[name] = self._CheckMandatorySubMsg( - part, 'new_partition_info', report, 'manifest.partitions') - - # Check: Old-style partition infos should not be specified. - for _, part in common.CROS_PARTITIONS: - self._CheckElemNotPresent(manifest, 'old_%s_info' % part, 'manifest') - self._CheckElemNotPresent(manifest, 'new_%s_info' % part, 'manifest') - - # Check: If old_partition_info is specified anywhere, it must be - # specified everywhere. - old_part_msgs = [part.msg for part in self.old_part_info.values() if part] - self._CheckPresentIffMany(old_part_msgs, 'old_partition_info', - 'manifest.partitions') + for part in manifest.partitions: + name = part.partition_name + self.old_part_info[name] = self._CheckOptionalSubMsg( + part, 'old_partition_info', report) + self.new_part_info[name] = self._CheckMandatorySubMsg( + part, 'new_partition_info', report, 'manifest.partitions') + + # Check: Old-style partition infos should not be specified. + for _, part in common.CROS_PARTITIONS: + self._CheckElemNotPresent(manifest, 'old_%s_info' % part, 'manifest') + self._CheckElemNotPresent(manifest, 'new_%s_info' % part, 'manifest') + + # Check: If old_partition_info is specified anywhere, it must be + # specified everywhere. + old_part_msgs = [part.msg for part in self.old_part_info.values() if part] + self._CheckPresentIffMany(old_part_msgs, 'old_partition_info', + 'manifest.partitions') is_delta = any(part and part.msg for part in self.old_part_info.values()) if is_delta: @@ -666,7 +652,7 @@ class PayloadChecker(object): 'Apparent full payload contains old_{kernel,rootfs}_info.') self.payload_type = _TYPE_DELTA - for part, (msg, part_report) in self.old_part_info.iteritems(): + for part, (msg, part_report) in self.old_part_info.items(): # Check: {size, hash} present in old_{kernel,rootfs}_info. field = 'old_%s_info' % part self.old_fs_sizes[part] = self._CheckMandatoryField(msg, 'size', @@ -687,7 +673,7 @@ class PayloadChecker(object): self.payload_type = _TYPE_FULL # Check: new_{kernel,rootfs}_info present; contains {size, hash}. - for part, (msg, part_report) in self.new_part_info.iteritems(): + for part, (msg, part_report) in self.new_part_info.items(): field = 'new_%s_info' % part self.new_fs_sizes[part] = self._CheckMandatoryField(msg, 'size', part_report, field) @@ -724,8 +710,7 @@ class PayloadChecker(object): self._CheckBlocksFitLength(length, total_blocks, self.block_size, '%s: %s' % (op_name, length_name)) - def _CheckExtents(self, extents, usable_size, block_counters, name, - allow_pseudo=False, allow_signature=False): + def _CheckExtents(self, extents, usable_size, block_counters, name): """Checks a sequence of extents. Args: @@ -733,8 +718,6 @@ class PayloadChecker(object): usable_size: The usable size of the partition to which the extents apply. block_counters: Array of counters corresponding to the number of blocks. name: The name of the extent block. - allow_pseudo: Whether or not pseudo block numbers are allowed. - allow_signature: Whether or not the extents are used for a signature. Returns: The total number of blocks in the extents. @@ -755,20 +738,15 @@ class PayloadChecker(object): if num_blocks == 0: raise error.PayloadError('%s: extent length is zero.' % ex_name) - if start_block != common.PSEUDO_EXTENT_MARKER: - # Check: Make sure we're within the partition limit. - if usable_size and end_block * self.block_size > usable_size: - raise error.PayloadError( - '%s: extent (%s) exceeds usable partition size (%d).' % - (ex_name, common.FormatExtent(ex, self.block_size), usable_size)) + # Check: Make sure we're within the partition limit. + if usable_size and end_block * self.block_size > usable_size: + raise error.PayloadError( + '%s: extent (%s) exceeds usable partition size (%d).' % + (ex_name, common.FormatExtent(ex, self.block_size), usable_size)) - # Record block usage. - for i in xrange(start_block, end_block): - block_counters[i] += 1 - elif not (allow_pseudo or (allow_signature and len(extents) == 1)): - # Pseudo-extents must be allowed explicitly, or otherwise be part of a - # signature operation (in which case there has to be exactly one). - raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name) + # Record block usage. + for i in range(start_block, end_block): + block_counters[i] += 1 total_num_blocks += num_blocks @@ -786,6 +764,11 @@ class PayloadChecker(object): Raises: error.PayloadError if any check fails. """ + # Check: total_dst_blocks is not a floating point. + if isinstance(total_dst_blocks, float): + raise error.PayloadError('%s: contains invalid data type of ' + 'total_dst_blocks.' % op_name) + # Check: Does not contain src extents. if op.src_extents: raise error.PayloadError('%s: contains src_extents.' % op_name) @@ -806,89 +789,6 @@ class PayloadChecker(object): 'space (%d * %d).' % (op_name, data_length, total_dst_blocks, self.block_size)) - def _CheckMoveOperation(self, op, data_offset, total_src_blocks, - total_dst_blocks, op_name): - """Specific checks for MOVE operations. - - Args: - op: The operation object from the manifest. - data_offset: The offset of a data blob for the operation. - total_src_blocks: Total number of blocks in src_extents. - total_dst_blocks: Total number of blocks in dst_extents. - op_name: Operation name for error reporting. - - Raises: - error.PayloadError if any check fails. - """ - # Check: No data_{offset,length}. - if data_offset is not None: - raise error.PayloadError('%s: contains data_{offset,length}.' % op_name) - - # Check: total_src_blocks == total_dst_blocks. - if total_src_blocks != total_dst_blocks: - raise error.PayloadError( - '%s: total src blocks (%d) != total dst blocks (%d).' % - (op_name, total_src_blocks, total_dst_blocks)) - - # Check: For all i, i-th src block index != i-th dst block index. - i = 0 - src_extent_iter = iter(op.src_extents) - dst_extent_iter = iter(op.dst_extents) - src_extent = dst_extent = None - src_idx = src_num = dst_idx = dst_num = 0 - while i < total_src_blocks: - # Get the next source extent, if needed. - if not src_extent: - try: - src_extent = src_extent_iter.next() - except StopIteration: - raise error.PayloadError('%s: ran out of src extents (%d/%d).' % - (op_name, i, total_src_blocks)) - src_idx = src_extent.start_block - src_num = src_extent.num_blocks - - # Get the next dest extent, if needed. - if not dst_extent: - try: - dst_extent = dst_extent_iter.next() - except StopIteration: - raise error.PayloadError('%s: ran out of dst extents (%d/%d).' % - (op_name, i, total_dst_blocks)) - dst_idx = dst_extent.start_block - dst_num = dst_extent.num_blocks - - # Check: start block is not 0. See crbug/480751; there are still versions - # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll, - # so we need to fail payloads that try to MOVE to/from block 0. - if src_idx == 0 or dst_idx == 0: - raise error.PayloadError( - '%s: MOVE operation cannot have extent with start block 0' % - op_name) - - if self.check_move_same_src_dst_block and src_idx == dst_idx: - raise error.PayloadError( - '%s: src/dst block number %d is the same (%d).' % - (op_name, i, src_idx)) - - advance = min(src_num, dst_num) - i += advance - - src_idx += advance - src_num -= advance - if src_num == 0: - src_extent = None - - dst_idx += advance - dst_num -= advance - if dst_num == 0: - dst_extent = None - - # Make sure we've exhausted all src/dst extents. - if src_extent: - raise error.PayloadError('%s: excess src blocks.' % op_name) - if dst_extent: - raise error.PayloadError('%s: excess dst blocks.' % op_name) - def _CheckZeroOperation(self, op, op_name): """Specific checks for ZERO operations. @@ -908,7 +808,7 @@ class PayloadChecker(object): raise error.PayloadError('%s: contains data_offset.' % op_name) def _CheckAnyDiffOperation(self, op, data_length, total_dst_blocks, op_name): - """Specific checks for BSDIFF, SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF + """Specific checks for SOURCE_BSDIFF, PUFFDIFF and BROTLI_BSDIFF operations. Args: @@ -933,8 +833,7 @@ class PayloadChecker(object): total_dst_blocks * self.block_size)) # Check the existence of src_length and dst_length for legacy bsdiffs. - if (op.type == common.OpType.BSDIFF or - (op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3)): + if op.type == common.OpType.SOURCE_BSDIFF and self.minor_version <= 3: if not op.HasField('src_length') or not op.HasField('dst_length'): raise error.PayloadError('%s: require {src,dst}_length.' % op_name) else: @@ -983,21 +882,19 @@ class PayloadChecker(object): if self.minor_version >= 3 and op.src_sha256_hash is None: raise error.PayloadError('%s: source hash missing.' % op_name) - def _CheckOperation(self, op, op_name, is_last, old_block_counters, - new_block_counters, old_usable_size, new_usable_size, - prev_data_offset, allow_signature, blob_hash_counts): + def _CheckOperation(self, op, op_name, old_block_counters, new_block_counters, + old_usable_size, new_usable_size, prev_data_offset, + blob_hash_counts): """Checks a single update operation. Args: op: The operation object. op_name: Operation name string for error reporting. - is_last: Whether this is the last operation in the sequence. old_block_counters: Arrays of block read counters. new_block_counters: Arrays of block write counters. old_usable_size: The overall usable size for src data in bytes. new_usable_size: The overall usable size for dst data in bytes. prev_data_offset: Offset of last used data bytes. - allow_signature: Whether this may be a signature operation. blob_hash_counts: Counters for hashed/unhashed blobs. Returns: @@ -1009,14 +906,10 @@ class PayloadChecker(object): # Check extents. total_src_blocks = self._CheckExtents( op.src_extents, old_usable_size, old_block_counters, - op_name + '.src_extents', allow_pseudo=True) - allow_signature_in_extents = (allow_signature and is_last and - op.type == common.OpType.REPLACE) + op_name + '.src_extents') total_dst_blocks = self._CheckExtents( op.dst_extents, new_usable_size, new_block_counters, - op_name + '.dst_extents', - allow_pseudo=(not self.check_dst_pseudo_extents), - allow_signature=allow_signature_in_extents) + op_name + '.dst_extents') # Check: data_offset present <==> data_length present. data_offset = self._CheckOptionalField(op, 'data_offset', None) @@ -1052,9 +945,7 @@ class PayloadChecker(object): (op_name, common.FormatSha256(op.data_sha256_hash), common.FormatSha256(actual_hash.digest()))) elif data_offset is not None: - if allow_signature_in_extents: - blob_hash_counts['signature'] += 1 - elif self.allow_unhashed: + if self.allow_unhashed: blob_hash_counts['unhashed'] += 1 else: raise error.PayloadError('%s: unhashed operation not allowed.' % @@ -1068,19 +959,11 @@ class PayloadChecker(object): (op_name, data_offset, prev_data_offset)) # Type-specific checks. - if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ): - self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name) - elif (op.type == common.OpType.REPLACE_XZ and - (self.minor_version >= 3 or - self.major_version >= common.BRILLO_MAJOR_PAYLOAD_VERSION)): + if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ, + common.OpType.REPLACE_XZ): self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name) - elif op.type == common.OpType.MOVE and self.minor_version == 1: - self._CheckMoveOperation(op, data_offset, total_src_blocks, - total_dst_blocks, op_name) elif op.type == common.OpType.ZERO and self.minor_version >= 4: self._CheckZeroOperation(op, op_name) - elif op.type == common.OpType.BSDIFF and self.minor_version == 1: - self._CheckAnyDiffOperation(op, data_length, total_dst_blocks, op_name) elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2: self._CheckSourceCopyOperation(data_offset, total_src_blocks, total_dst_blocks, op_name) @@ -1102,7 +985,7 @@ class PayloadChecker(object): def _SizeToNumBlocks(self, size): """Returns the number of blocks needed to contain a given byte size.""" - return (size + self.block_size - 1) / self.block_size + return (size + self.block_size - 1) // self.block_size def _AllocBlockCounters(self, total_size): """Returns a freshly initialized array of block counters. @@ -1122,7 +1005,7 @@ class PayloadChecker(object): def _CheckOperations(self, operations, report, base_name, old_fs_size, new_fs_size, old_usable_size, new_usable_size, - prev_data_offset, allow_signature): + prev_data_offset): """Checks a sequence of update operations. Args: @@ -1134,7 +1017,6 @@ class PayloadChecker(object): old_usable_size: The overall usable size of the old partition in bytes. new_usable_size: The overall usable size of the new partition in bytes. prev_data_offset: Offset of last used data bytes. - allow_signature: Whether this sequence may contain signature operations. Returns: The total data blob size used. @@ -1149,9 +1031,7 @@ class PayloadChecker(object): common.OpType.REPLACE: 0, common.OpType.REPLACE_BZ: 0, common.OpType.REPLACE_XZ: 0, - common.OpType.MOVE: 0, common.OpType.ZERO: 0, - common.OpType.BSDIFF: 0, common.OpType.SOURCE_COPY: 0, common.OpType.SOURCE_BSDIFF: 0, common.OpType.PUFFDIFF: 0, @@ -1162,8 +1042,6 @@ class PayloadChecker(object): common.OpType.REPLACE: 0, common.OpType.REPLACE_BZ: 0, common.OpType.REPLACE_XZ: 0, - # MOVE operations don't have blobs. - common.OpType.BSDIFF: 0, # SOURCE_COPY operations don't have blobs. common.OpType.SOURCE_BSDIFF: 0, common.OpType.PUFFDIFF: 0, @@ -1174,8 +1052,6 @@ class PayloadChecker(object): 'hashed': 0, 'unhashed': 0, } - if allow_signature: - blob_hash_counts['signature'] = 0 # Allocate old and new block counters. old_block_counters = (self._AllocBlockCounters(old_usable_size) @@ -1188,16 +1064,14 @@ class PayloadChecker(object): op_num += 1 # Check: Type is valid. - if op.type not in op_counts.keys(): + if op.type not in op_counts: raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type)) op_counts[op.type] += 1 - is_last = op_num == len(operations) curr_data_used = self._CheckOperation( - op, op_name, is_last, old_block_counters, new_block_counters, + op, op_name, old_block_counters, new_block_counters, old_usable_size, new_usable_size, - prev_data_offset + total_data_used, allow_signature, - blob_hash_counts) + prev_data_offset + total_data_used, blob_hash_counts) if curr_data_used: op_blob_totals[op.type] += curr_data_used total_data_used += curr_data_used @@ -1251,21 +1125,17 @@ class PayloadChecker(object): if not sigs.signatures: raise error.PayloadError('Signature block is empty.') - last_ops_section = (self.payload.manifest.kernel_install_operations or - self.payload.manifest.install_operations) - - # Only major version 1 has the fake signature OP at the end. - if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION: - fake_sig_op = last_ops_section[-1] + # Check that we don't have the signature operation blob at the end (used to + # be for major version 1). + last_partition = self.payload.manifest.partitions[-1] + if last_partition.operations: + last_op = last_partition.operations[-1] # Check: signatures_{offset,size} must match the last (fake) operation. - if not (fake_sig_op.type == common.OpType.REPLACE and - self.sigs_offset == fake_sig_op.data_offset and - self.sigs_size == fake_sig_op.data_length): - raise error.PayloadError('Signatures_{offset,size} (%d+%d) does not' - ' match last operation (%d+%d).' % - (self.sigs_offset, self.sigs_size, - fake_sig_op.data_offset, - fake_sig_op.data_length)) + if (last_op.type == common.OpType.REPLACE and + last_op.data_offset == self.sigs_offset and + last_op.data_length == self.sigs_size): + raise error.PayloadError('It seems like the last operation is the ' + 'signature blob. This is an invalid payload.') # Compute the checksum of all data up to signature blob. # TODO(garnold) we're re-reading the whole data section into a string @@ -1280,17 +1150,13 @@ class PayloadChecker(object): sig_report = report.AddSubReport(sig_name) # Check: Signature contains mandatory fields. - self._CheckMandatoryField(sig, 'version', sig_report, sig_name) self._CheckMandatoryField(sig, 'data', None, sig_name) sig_report.AddField('data len', len(sig.data)) # Check: Signatures pertains to actual payload hash. - if sig.version == 1: + if sig.data: self._CheckSha256Signature(sig.data, pubkey_file_name, payload_hasher.digest(), sig_name) - else: - raise error.PayloadError('Unknown signature version (%d).' % - sig.version) def Run(self, pubkey_file_name=None, metadata_sig_file=None, metadata_size=0, part_sizes=None, report_out_file=None): @@ -1344,62 +1210,38 @@ class PayloadChecker(object): self._CheckManifest(report, part_sizes) assert self.payload_type, 'payload type should be known by now' - manifest = self.payload.manifest - - # Part 3: Examine partition operations. - install_operations = [] - if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION: - # partitions field should not ever exist in major version 1 payloads - self._CheckRepeatedElemNotPresent(manifest, 'partitions', 'manifest') - - install_operations.append((common.ROOTFS, manifest.install_operations)) - install_operations.append((common.KERNEL, - manifest.kernel_install_operations)) - - else: - self._CheckRepeatedElemNotPresent(manifest, 'install_operations', + # Make sure deprecated values are not present in the payload. + for field in ('install_operations', 'kernel_install_operations'): + self._CheckRepeatedElemNotPresent(self.payload.manifest, field, 'manifest') - self._CheckRepeatedElemNotPresent(manifest, 'kernel_install_operations', - 'manifest') - - for update in manifest.partitions: - install_operations.append((update.partition_name, update.operations)) + for field in ('old_kernel_info', 'old_rootfs_info', + 'new_kernel_info', 'new_rootfs_info'): + self._CheckElemNotPresent(self.payload.manifest, field, 'manifest') total_blob_size = 0 - for part, operations in install_operations: + for part, operations in ((p.partition_name, p.operations) + for p in self.payload.manifest.partitions): report.AddSection('%s operations' % part) new_fs_usable_size = self.new_fs_sizes[part] old_fs_usable_size = self.old_fs_sizes[part] - if part_sizes.get(part, None): + if part_sizes is not None and part_sizes.get(part, None): new_fs_usable_size = old_fs_usable_size = part_sizes[part] - # Infer the usable partition size when validating rootfs operations: - # - If rootfs partition size was provided, use that. - # - Otherwise, if this is an older delta (minor version < 2), stick with - # a known constant size. This is necessary because older deltas may - # exceed the filesystem size when moving data blocks around. - # - Otherwise, use the encoded filesystem size. - elif self.payload_type == _TYPE_DELTA and part == common.ROOTFS and \ - self.minor_version in (None, 1): - new_fs_usable_size = old_fs_usable_size = _OLD_DELTA_USABLE_PART_SIZE - - # TODO(garnold)(chromium:243559) only default to the filesystem size if - # no explicit size provided *and* the partition size is not embedded in - # the payload; see issue for more details. + + # TODO(chromium:243559) only default to the filesystem size if no + # explicit size provided *and* the partition size is not embedded in the + # payload; see issue for more details. total_blob_size += self._CheckOperations( operations, report, '%s_install_operations' % part, self.old_fs_sizes[part], self.new_fs_sizes[part], - old_fs_usable_size, new_fs_usable_size, total_blob_size, - (self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION - and part == common.KERNEL)) + old_fs_usable_size, new_fs_usable_size, total_blob_size) # Check: Operations data reach the end of the payload file. used_payload_size = self.payload.data_offset + total_blob_size # Major versions 2 and higher have a signature at the end, so it should be # considered in the total size of the image. - if (self.major_version >= common.BRILLO_MAJOR_PAYLOAD_VERSION and - self.sigs_size): + if self.sigs_size: used_payload_size += self.sigs_size if used_payload_size != payload_file_size: diff --git a/scripts/update_payload/checker_unittest.py b/scripts/update_payload/checker_unittest.py index 7e52233e..993b785c 100755 --- a/scripts/update_payload/checker_unittest.py +++ b/scripts/update_payload/checker_unittest.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/env python # # Copyright (C) 2013 The Android Open Source Project # @@ -17,35 +17,36 @@ """Unit testing checker.py.""" -from __future__ import print_function +# Disable check for function names to avoid errors based on old code +# pylint: disable-msg=invalid-name + +from __future__ import absolute_import import array import collections -import cStringIO import hashlib +import io import itertools import os import unittest -# pylint cannot find mox. -# pylint: disable=F0401 -import mox +from six.moves import zip + +import mock # pylint: disable=import-error from update_payload import checker from update_payload import common from update_payload import test_utils from update_payload import update_metadata_pb2 from update_payload.error import PayloadError -from update_payload.payload import Payload # Avoid name conflicts later. +from update_payload.payload import Payload # Avoid name conflicts later. def _OpTypeByName(op_name): - """Returns the type of an operation from itsname.""" + """Returns the type of an operation from its name.""" op_name_to_type = { 'REPLACE': common.OpType.REPLACE, 'REPLACE_BZ': common.OpType.REPLACE_BZ, - 'MOVE': common.OpType.MOVE, - 'BSDIFF': common.OpType.BSDIFF, 'SOURCE_COPY': common.OpType.SOURCE_COPY, 'SOURCE_BSDIFF': common.OpType.SOURCE_BSDIFF, 'ZERO': common.OpType.ZERO, @@ -65,7 +66,7 @@ def _GetPayloadChecker(payload_gen_write_to_file_func, payload_gen_dargs=None, if checker_init_dargs is None: checker_init_dargs = {} - payload_file = cStringIO.StringIO() + payload_file = io.BytesIO() payload_gen_write_to_file_func(payload_file, **payload_gen_dargs) payload_file.seek(0) payload = Payload(payload_file) @@ -75,7 +76,7 @@ def _GetPayloadChecker(payload_gen_write_to_file_func, payload_gen_dargs=None, def _GetPayloadCheckerWithData(payload_gen): """Returns a payload checker from a given payload generator.""" - payload_file = cStringIO.StringIO() + payload_file = io.BytesIO() payload_gen.WriteToFile(payload_file) payload_file.seek(0) payload = Payload(payload_file) @@ -89,7 +90,7 @@ def _GetPayloadCheckerWithData(payload_gen): # pylint: disable=W0212 # Don't bark about missing members of classes you cannot import. # pylint: disable=E1101 -class PayloadCheckerTest(mox.MoxTestBase): +class PayloadCheckerTest(unittest.TestCase): """Tests the PayloadChecker class. In addition to ordinary testFoo() methods, which are automatically invoked by @@ -102,11 +103,42 @@ class PayloadCheckerTest(mox.MoxTestBase): all such tests is done in AddAllParametricTests(). """ + def setUp(self): + """setUp function for unittest testcase""" + self.mock_checks = [] + + def tearDown(self): + """tearDown function for unittest testcase""" + # Verify that all mock functions were called. + for check in self.mock_checks: + check.mock_fn.assert_called_once_with(*check.exp_args, **check.exp_kwargs) + + class MockChecksAtTearDown(object): + """Mock data storage. + + This class stores the mock functions and its arguments to be checked at a + later point. + """ + def __init__(self, mock_fn, *args, **kwargs): + self.mock_fn = mock_fn + self.exp_args = args + self.exp_kwargs = kwargs + + def addPostCheckForMockFunction(self, mock_fn, *args, **kwargs): + """Store a mock function and its arguments to self.mock_checks + + Args: + mock_fn: mock function object + args: expected positional arguments for the mock_fn + kwargs: expected named arguments for the mock_fn + """ + self.mock_checks.append(self.MockChecksAtTearDown(mock_fn, *args, **kwargs)) + def MockPayload(self): """Create a mock payload object, complete with a mock manifest.""" - payload = self.mox.CreateMock(Payload) + payload = mock.create_autospec(Payload) payload.is_init = True - payload.manifest = self.mox.CreateMock( + payload.manifest = mock.create_autospec( update_metadata_pb2.DeltaArchiveManifest) return payload @@ -175,19 +207,20 @@ class PayloadCheckerTest(mox.MoxTestBase): subreport = 'fake subreport' # Create a mock message. - msg = self.mox.CreateMock(update_metadata_pb2._message.Message) - msg.HasField(name).AndReturn(is_present) + msg = mock.create_autospec(update_metadata_pb2._message.Message) + self.addPostCheckForMockFunction(msg.HasField, name) + msg.HasField.return_value = is_present setattr(msg, name, val) - # Create a mock report. - report = self.mox.CreateMock(checker._PayloadReport) + report = mock.create_autospec(checker._PayloadReport) if is_present: if is_submsg: - report.AddSubReport(name).AndReturn(subreport) + self.addPostCheckForMockFunction(report.AddSubReport, name) + report.AddSubReport.return_value = subreport else: - report.AddField(name, convert(val), linebreak=linebreak, indent=indent) + self.addPostCheckForMockFunction(report.AddField, name, convert(val), + linebreak=linebreak, indent=indent) - self.mox.ReplayAll() return (msg, report, subreport, name, val) def DoAddElemTest(self, is_present, is_mandatory, is_submsg, convert, @@ -213,9 +246,9 @@ class PayloadCheckerTest(mox.MoxTestBase): else: ret_val, ret_subreport = checker.PayloadChecker._CheckElem(*args, **kwargs) - self.assertEquals(val if is_present else None, ret_val) - self.assertEquals(subreport if is_present and is_submsg else None, - ret_subreport) + self.assertEqual(val if is_present else None, ret_val) + self.assertEqual(subreport if is_present and is_submsg else None, + ret_subreport) def DoAddFieldTest(self, is_mandatory, is_present, convert, linebreak, indent): @@ -245,7 +278,7 @@ class PayloadCheckerTest(mox.MoxTestBase): self.assertRaises(PayloadError, tested_func, *args, **kwargs) else: ret_val = tested_func(*args, **kwargs) - self.assertEquals(val if is_present else None, ret_val) + self.assertEqual(val if is_present else None, ret_val) def DoAddSubMsgTest(self, is_mandatory, is_present): """Parametrized testing of _Check{Mandatory,Optional}SubMsg(). @@ -269,8 +302,8 @@ class PayloadCheckerTest(mox.MoxTestBase): self.assertRaises(PayloadError, tested_func, *args) else: ret_val, ret_subreport = tested_func(*args) - self.assertEquals(val if is_present else None, ret_val) - self.assertEquals(subreport if is_present else None, ret_subreport) + self.assertEqual(val if is_present else None, ret_val) + self.assertEqual(subreport if is_present else None, ret_subreport) def testCheckPresentIff(self): """Tests _CheckPresentIff().""" @@ -296,15 +329,14 @@ class PayloadCheckerTest(mox.MoxTestBase): returned_signed_hash: The signed hash data retuned by openssl. expected_signed_hash: The signed hash data to compare against. """ - try: - # Stub out the subprocess invocation. - self.mox.StubOutWithMock(checker.PayloadChecker, '_Run') + # Stub out the subprocess invocation. + with mock.patch.object(checker.PayloadChecker, '_Run') \ + as mock_payload_checker: if expect_subprocess_call: - checker.PayloadChecker._Run( - mox.IsA(list), send_data=sig_data).AndReturn( - (sig_asn1_header + returned_signed_hash, None)) + mock_payload_checker([], send_data=sig_data) + mock_payload_checker.return_value = ( + sig_asn1_header + returned_signed_hash, None) - self.mox.ReplayAll() if expect_pass: self.assertIsNone(checker.PayloadChecker._CheckSha256Signature( sig_data, 'foo', expected_signed_hash, 'bar')) @@ -312,13 +344,11 @@ class PayloadCheckerTest(mox.MoxTestBase): self.assertRaises(PayloadError, checker.PayloadChecker._CheckSha256Signature, sig_data, 'foo', expected_signed_hash, 'bar') - finally: - self.mox.UnsetStubs() def testCheckSha256Signature_Pass(self): """Tests _CheckSha256Signature(); pass case.""" sig_data = 'fake-signature'.ljust(256) - signed_hash = hashlib.sha256('fake-data').digest() + signed_hash = hashlib.sha256(b'fake-data').digest() self.DoCheckSha256SignatureTest(True, True, sig_data, common.SIG_ASN1_HEADER, signed_hash, signed_hash) @@ -326,7 +356,7 @@ class PayloadCheckerTest(mox.MoxTestBase): def testCheckSha256Signature_FailBadSignature(self): """Tests _CheckSha256Signature(); fails due to malformed signature.""" sig_data = 'fake-signature' # Malformed (not 256 bytes in length). - signed_hash = hashlib.sha256('fake-data').digest() + signed_hash = hashlib.sha256(b'fake-data').digest() self.DoCheckSha256SignatureTest(False, False, sig_data, common.SIG_ASN1_HEADER, signed_hash, signed_hash) @@ -334,7 +364,7 @@ class PayloadCheckerTest(mox.MoxTestBase): def testCheckSha256Signature_FailBadOutputLength(self): """Tests _CheckSha256Signature(); fails due to unexpected output length.""" sig_data = 'fake-signature'.ljust(256) - signed_hash = 'fake-hash' # Malformed (not 32 bytes in length). + signed_hash = b'fake-hash' # Malformed (not 32 bytes in length). self.DoCheckSha256SignatureTest(False, True, sig_data, common.SIG_ASN1_HEADER, signed_hash, signed_hash) @@ -342,16 +372,16 @@ class PayloadCheckerTest(mox.MoxTestBase): def testCheckSha256Signature_FailBadAsnHeader(self): """Tests _CheckSha256Signature(); fails due to bad ASN1 header.""" sig_data = 'fake-signature'.ljust(256) - signed_hash = hashlib.sha256('fake-data').digest() - bad_asn1_header = 'bad-asn-header'.ljust(len(common.SIG_ASN1_HEADER)) + signed_hash = hashlib.sha256(b'fake-data').digest() + bad_asn1_header = b'bad-asn-header'.ljust(len(common.SIG_ASN1_HEADER)) self.DoCheckSha256SignatureTest(False, True, sig_data, bad_asn1_header, signed_hash, signed_hash) def testCheckSha256Signature_FailBadHash(self): """Tests _CheckSha256Signature(); fails due to bad hash returned.""" sig_data = 'fake-signature'.ljust(256) - expected_signed_hash = hashlib.sha256('fake-data').digest() - returned_signed_hash = hashlib.sha256('bad-fake-data').digest() + expected_signed_hash = hashlib.sha256(b'fake-data').digest() + returned_signed_hash = hashlib.sha256(b'bad-fake-data').digest() self.DoCheckSha256SignatureTest(False, True, sig_data, common.SIG_ASN1_HEADER, expected_signed_hash, returned_signed_hash) @@ -429,10 +459,10 @@ class PayloadCheckerTest(mox.MoxTestBase): payload_gen.SetBlockSize(test_utils.KiB(4)) # Add some operations. - payload_gen.AddOperation(False, common.OpType.MOVE, + payload_gen.AddOperation(common.ROOTFS, common.OpType.SOURCE_COPY, src_extents=[(0, 16), (16, 497)], dst_extents=[(16, 496), (0, 16)]) - payload_gen.AddOperation(True, common.OpType.MOVE, + payload_gen.AddOperation(common.KERNEL, common.OpType.SOURCE_COPY, src_extents=[(0, 8), (8, 8)], dst_extents=[(8, 8), (0, 8)]) @@ -457,21 +487,23 @@ class PayloadCheckerTest(mox.MoxTestBase): # Add old kernel/rootfs partition info, as required. if fail_mismatched_oki_ori or fail_old_kernel_fs_size or fail_bad_oki: oki_hash = (None if fail_bad_oki - else hashlib.sha256('fake-oki-content').digest()) - payload_gen.SetPartInfo(True, False, old_kernel_fs_size, oki_hash) + else hashlib.sha256(b'fake-oki-content').digest()) + payload_gen.SetPartInfo(common.KERNEL, False, old_kernel_fs_size, + oki_hash) if not fail_mismatched_oki_ori and (fail_old_rootfs_fs_size or fail_bad_ori): ori_hash = (None if fail_bad_ori - else hashlib.sha256('fake-ori-content').digest()) - payload_gen.SetPartInfo(False, False, old_rootfs_fs_size, ori_hash) + else hashlib.sha256(b'fake-ori-content').digest()) + payload_gen.SetPartInfo(common.ROOTFS, False, old_rootfs_fs_size, + ori_hash) # Add new kernel/rootfs partition info. payload_gen.SetPartInfo( - True, True, new_kernel_fs_size, - None if fail_bad_nki else hashlib.sha256('fake-nki-content').digest()) + common.KERNEL, True, new_kernel_fs_size, + None if fail_bad_nki else hashlib.sha256(b'fake-nki-content').digest()) payload_gen.SetPartInfo( - False, True, new_rootfs_fs_size, - None if fail_bad_nri else hashlib.sha256('fake-nri-content').digest()) + common.ROOTFS, True, new_rootfs_fs_size, + None if fail_bad_nri else hashlib.sha256(b'fake-nri-content').digest()) # Set the minor version. payload_gen.SetMinorVersion(0) @@ -518,28 +550,11 @@ class PayloadCheckerTest(mox.MoxTestBase): # Passes w/ all real extents. extents = self.NewExtentList((0, 4), (8, 3), (1024, 16)) - self.assertEquals( + self.assertEqual( 23, payload_checker._CheckExtents(extents, (1024 + 16) * block_size, collections.defaultdict(int), 'foo')) - # Passes w/ pseudo-extents (aka sparse holes). - extents = self.NewExtentList((0, 4), (common.PSEUDO_EXTENT_MARKER, 5), - (8, 3)) - self.assertEquals( - 12, - payload_checker._CheckExtents(extents, (1024 + 16) * block_size, - collections.defaultdict(int), 'foo', - allow_pseudo=True)) - - # Passes w/ pseudo-extent due to a signature. - extents = self.NewExtentList((common.PSEUDO_EXTENT_MARKER, 2)) - self.assertEquals( - 2, - payload_checker._CheckExtents(extents, (1024 + 16) * block_size, - collections.defaultdict(int), 'foo', - allow_signature=True)) - # Fails, extent missing a start block. extents = self.NewExtentList((-1, 4), (8, 3), (1024, 16)) self.assertRaises( @@ -570,34 +585,34 @@ class PayloadCheckerTest(mox.MoxTestBase): block_size = payload_checker.block_size data_length = 10000 - op = self.mox.CreateMock( - update_metadata_pb2.InstallOperation) + op = mock.create_autospec(update_metadata_pb2.InstallOperation) op.type = common.OpType.REPLACE # Pass. op.src_extents = [] self.assertIsNone( payload_checker._CheckReplaceOperation( - op, data_length, (data_length + block_size - 1) / block_size, + op, data_length, (data_length + block_size - 1) // block_size, 'foo')) # Fail, src extents founds. op.src_extents = ['bar'] self.assertRaises( PayloadError, payload_checker._CheckReplaceOperation, - op, data_length, (data_length + block_size - 1) / block_size, 'foo') + op, data_length, (data_length + block_size - 1) // block_size, 'foo') # Fail, missing data. op.src_extents = [] self.assertRaises( PayloadError, payload_checker._CheckReplaceOperation, - op, None, (data_length + block_size - 1) / block_size, 'foo') + op, None, (data_length + block_size - 1) // block_size, 'foo') # Fail, length / block number mismatch. op.src_extents = ['bar'] self.assertRaises( PayloadError, payload_checker._CheckReplaceOperation, - op, data_length, (data_length + block_size - 1) / block_size + 1, 'foo') + op, data_length, (data_length + block_size - 1) // block_size + 1, + 'foo') def testCheckReplaceBzOperation(self): """Tests _CheckReplaceOperation() where op.type == REPLACE_BZ.""" @@ -605,7 +620,7 @@ class PayloadCheckerTest(mox.MoxTestBase): block_size = payload_checker.block_size data_length = block_size * 3 - op = self.mox.CreateMock( + op = mock.create_autospec( update_metadata_pb2.InstallOperation) op.type = common.OpType.REPLACE_BZ @@ -613,25 +628,32 @@ class PayloadCheckerTest(mox.MoxTestBase): op.src_extents = [] self.assertIsNone( payload_checker._CheckReplaceOperation( - op, data_length, (data_length + block_size - 1) / block_size + 5, + op, data_length, (data_length + block_size - 1) // block_size + 5, 'foo')) # Fail, src extents founds. op.src_extents = ['bar'] self.assertRaises( PayloadError, payload_checker._CheckReplaceOperation, - op, data_length, (data_length + block_size - 1) / block_size + 5, 'foo') + op, data_length, (data_length + block_size - 1) // block_size + 5, + 'foo') # Fail, missing data. op.src_extents = [] self.assertRaises( PayloadError, payload_checker._CheckReplaceOperation, - op, None, (data_length + block_size - 1) / block_size, 'foo') + op, None, (data_length + block_size - 1) // block_size, 'foo') # Fail, too few blocks to justify BZ. op.src_extents = [] self.assertRaises( PayloadError, payload_checker._CheckReplaceOperation, + op, data_length, (data_length + block_size - 1) // block_size, 'foo') + + # Fail, total_dst_blocks is a floating point value. + op.src_extents = [] + self.assertRaises( + PayloadError, payload_checker._CheckReplaceOperation, op, data_length, (data_length + block_size - 1) / block_size, 'foo') def testCheckReplaceXzOperation(self): @@ -640,7 +662,7 @@ class PayloadCheckerTest(mox.MoxTestBase): block_size = payload_checker.block_size data_length = block_size * 3 - op = self.mox.CreateMock( + op = mock.create_autospec( update_metadata_pb2.InstallOperation) op.type = common.OpType.REPLACE_XZ @@ -648,152 +670,33 @@ class PayloadCheckerTest(mox.MoxTestBase): op.src_extents = [] self.assertIsNone( payload_checker._CheckReplaceOperation( - op, data_length, (data_length + block_size - 1) / block_size + 5, + op, data_length, (data_length + block_size - 1) // block_size + 5, 'foo')) # Fail, src extents founds. op.src_extents = ['bar'] self.assertRaises( PayloadError, payload_checker._CheckReplaceOperation, - op, data_length, (data_length + block_size - 1) / block_size + 5, 'foo') + op, data_length, (data_length + block_size - 1) // block_size + 5, + 'foo') # Fail, missing data. op.src_extents = [] self.assertRaises( PayloadError, payload_checker._CheckReplaceOperation, - op, None, (data_length + block_size - 1) / block_size, 'foo') + op, None, (data_length + block_size - 1) // block_size, 'foo') # Fail, too few blocks to justify XZ. op.src_extents = [] self.assertRaises( PayloadError, payload_checker._CheckReplaceOperation, - op, data_length, (data_length + block_size - 1) / block_size, 'foo') - - def testCheckMoveOperation_Pass(self): - """Tests _CheckMoveOperation(); pass case.""" - payload_checker = checker.PayloadChecker(self.MockPayload()) - op = update_metadata_pb2.InstallOperation() - op.type = common.OpType.MOVE - - self.AddToMessage(op.src_extents, - self.NewExtentList((1, 4), (12, 2), (1024, 128))) - self.AddToMessage(op.dst_extents, - self.NewExtentList((16, 128), (512, 6))) - self.assertIsNone( - payload_checker._CheckMoveOperation(op, None, 134, 134, 'foo')) - - def testCheckMoveOperation_FailContainsData(self): - """Tests _CheckMoveOperation(); fails, message contains data.""" - payload_checker = checker.PayloadChecker(self.MockPayload()) - op = update_metadata_pb2.InstallOperation() - op.type = common.OpType.MOVE - - self.AddToMessage(op.src_extents, - self.NewExtentList((1, 4), (12, 2), (1024, 128))) - self.AddToMessage(op.dst_extents, - self.NewExtentList((16, 128), (512, 6))) - self.assertRaises( - PayloadError, payload_checker._CheckMoveOperation, - op, 1024, 134, 134, 'foo') - - def testCheckMoveOperation_FailInsufficientSrcBlocks(self): - """Tests _CheckMoveOperation(); fails, not enough actual src blocks.""" - payload_checker = checker.PayloadChecker(self.MockPayload()) - op = update_metadata_pb2.InstallOperation() - op.type = common.OpType.MOVE + op, data_length, (data_length + block_size - 1) // block_size, 'foo') - self.AddToMessage(op.src_extents, - self.NewExtentList((1, 4), (12, 2), (1024, 127))) - self.AddToMessage(op.dst_extents, - self.NewExtentList((16, 128), (512, 6))) - self.assertRaises( - PayloadError, payload_checker._CheckMoveOperation, - op, None, 134, 134, 'foo') - - def testCheckMoveOperation_FailInsufficientDstBlocks(self): - """Tests _CheckMoveOperation(); fails, not enough actual dst blocks.""" - payload_checker = checker.PayloadChecker(self.MockPayload()) - op = update_metadata_pb2.InstallOperation() - op.type = common.OpType.MOVE - - self.AddToMessage(op.src_extents, - self.NewExtentList((1, 4), (12, 2), (1024, 128))) - self.AddToMessage(op.dst_extents, - self.NewExtentList((16, 128), (512, 5))) - self.assertRaises( - PayloadError, payload_checker._CheckMoveOperation, - op, None, 134, 134, 'foo') - - def testCheckMoveOperation_FailExcessSrcBlocks(self): - """Tests _CheckMoveOperation(); fails, too many actual src blocks.""" - payload_checker = checker.PayloadChecker(self.MockPayload()) - op = update_metadata_pb2.InstallOperation() - op.type = common.OpType.MOVE - - self.AddToMessage(op.src_extents, - self.NewExtentList((1, 4), (12, 2), (1024, 128))) - self.AddToMessage(op.dst_extents, - self.NewExtentList((16, 128), (512, 5))) - self.assertRaises( - PayloadError, payload_checker._CheckMoveOperation, - op, None, 134, 134, 'foo') - self.AddToMessage(op.src_extents, - self.NewExtentList((1, 4), (12, 2), (1024, 129))) - self.AddToMessage(op.dst_extents, - self.NewExtentList((16, 128), (512, 6))) - self.assertRaises( - PayloadError, payload_checker._CheckMoveOperation, - op, None, 134, 134, 'foo') - - def testCheckMoveOperation_FailExcessDstBlocks(self): - """Tests _CheckMoveOperation(); fails, too many actual dst blocks.""" - payload_checker = checker.PayloadChecker(self.MockPayload()) - op = update_metadata_pb2.InstallOperation() - op.type = common.OpType.MOVE - - self.AddToMessage(op.src_extents, - self.NewExtentList((1, 4), (12, 2), (1024, 128))) - self.AddToMessage(op.dst_extents, - self.NewExtentList((16, 128), (512, 7))) - self.assertRaises( - PayloadError, payload_checker._CheckMoveOperation, - op, None, 134, 134, 'foo') - - def testCheckMoveOperation_FailStagnantBlocks(self): - """Tests _CheckMoveOperation(); fails, there are blocks that do not move.""" - payload_checker = checker.PayloadChecker(self.MockPayload()) - op = update_metadata_pb2.InstallOperation() - op.type = common.OpType.MOVE - - self.AddToMessage(op.src_extents, - self.NewExtentList((1, 4), (12, 2), (1024, 128))) - self.AddToMessage(op.dst_extents, - self.NewExtentList((8, 128), (512, 6))) - self.assertRaises( - PayloadError, payload_checker._CheckMoveOperation, - op, None, 134, 134, 'foo') - - def testCheckMoveOperation_FailZeroStartBlock(self): - """Tests _CheckMoveOperation(); fails, has extent with start block 0.""" - payload_checker = checker.PayloadChecker(self.MockPayload()) - op = update_metadata_pb2.InstallOperation() - op.type = common.OpType.MOVE - - self.AddToMessage(op.src_extents, - self.NewExtentList((0, 4), (12, 2), (1024, 128))) - self.AddToMessage(op.dst_extents, - self.NewExtentList((8, 128), (512, 6))) - self.assertRaises( - PayloadError, payload_checker._CheckMoveOperation, - op, None, 134, 134, 'foo') - - self.AddToMessage(op.src_extents, - self.NewExtentList((1, 4), (12, 2), (1024, 128))) - self.AddToMessage(op.dst_extents, - self.NewExtentList((0, 128), (512, 6))) + # Fail, total_dst_blocks is a floating point value. + op.src_extents = [] self.assertRaises( - PayloadError, payload_checker._CheckMoveOperation, - op, None, 134, 134, 'foo') + PayloadError, payload_checker._CheckReplaceOperation, + op, data_length, (data_length + block_size - 1) / block_size, 'foo') def testCheckAnyDiff(self): """Tests _CheckAnyDiffOperation().""" @@ -832,8 +735,8 @@ class PayloadCheckerTest(mox.MoxTestBase): self.assertRaises(PayloadError, payload_checker._CheckSourceCopyOperation, None, 0, 1, 'foo') - def DoCheckOperationTest(self, op_type_name, is_last, allow_signature, - allow_unhashed, fail_src_extents, fail_dst_extents, + def DoCheckOperationTest(self, op_type_name, allow_unhashed, + fail_src_extents, fail_dst_extents, fail_mismatched_data_offset_length, fail_missing_dst_extents, fail_src_length, fail_dst_length, fail_data_hash, @@ -841,10 +744,8 @@ class PayloadCheckerTest(mox.MoxTestBase): """Parametric testing of _CheckOperation(). Args: - op_type_name: 'REPLACE', 'REPLACE_BZ', 'REPLACE_XZ', 'MOVE', 'BSDIFF', + op_type_name: 'REPLACE', 'REPLACE_BZ', 'REPLACE_XZ', 'SOURCE_COPY', 'SOURCE_BSDIFF', BROTLI_BSDIFF or 'PUFFDIFF'. - is_last: Whether we're testing the last operation in a sequence. - allow_signature: Whether we're testing a signature-capable operation. allow_unhashed: Whether we're allowing to not hash the data. fail_src_extents: Tamper with src extents. fail_dst_extents: Tamper with dst extents. @@ -869,9 +770,9 @@ class PayloadCheckerTest(mox.MoxTestBase): old_part_size = test_utils.MiB(4) new_part_size = test_utils.MiB(8) old_block_counters = array.array( - 'B', [0] * ((old_part_size + block_size - 1) / block_size)) + 'B', [0] * ((old_part_size + block_size - 1) // block_size)) new_block_counters = array.array( - 'B', [0] * ((new_part_size + block_size - 1) / block_size)) + 'B', [0] * ((new_part_size + block_size - 1) // block_size)) prev_data_offset = 1876 blob_hash_counts = collections.defaultdict(int) @@ -880,8 +781,7 @@ class PayloadCheckerTest(mox.MoxTestBase): op.type = op_type total_src_blocks = 0 - if op_type in (common.OpType.MOVE, common.OpType.BSDIFF, - common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF, + if op_type in (common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF, common.OpType.PUFFDIFF, common.OpType.BROTLI_BSDIFF): if fail_src_extents: self.AddToMessage(op.src_extents, @@ -891,12 +791,9 @@ class PayloadCheckerTest(mox.MoxTestBase): self.NewExtentList((1, 16))) total_src_blocks = 16 - # TODO(tbrindus): add major version 2 tests. - payload_checker.major_version = common.CHROMEOS_MAJOR_PAYLOAD_VERSION + payload_checker.major_version = common.BRILLO_MAJOR_PAYLOAD_VERSION if op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ): payload_checker.minor_version = 0 - elif op_type in (common.OpType.MOVE, common.OpType.BSDIFF): - payload_checker.minor_version = 2 if fail_bad_minor_version else 1 elif op_type in (common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF): payload_checker.minor_version = 1 if fail_bad_minor_version else 2 if op_type == common.OpType.REPLACE_XZ: @@ -907,7 +804,7 @@ class PayloadCheckerTest(mox.MoxTestBase): elif op_type == common.OpType.PUFFDIFF: payload_checker.minor_version = 4 if fail_bad_minor_version else 5 - if op_type not in (common.OpType.MOVE, common.OpType.SOURCE_COPY): + if op_type != common.OpType.SOURCE_COPY: if not fail_mismatched_data_offset_length: op.data_length = 16 * block_size - 8 if fail_prev_data_offset: @@ -916,20 +813,16 @@ class PayloadCheckerTest(mox.MoxTestBase): op.data_offset = prev_data_offset fake_data = 'fake-data'.ljust(op.data_length) - if not (allow_unhashed or (is_last and allow_signature and - op_type == common.OpType.REPLACE)): - if not fail_data_hash: - # Create a valid data blob hash. - op.data_sha256_hash = hashlib.sha256(fake_data).digest() - payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn( - fake_data) + if not allow_unhashed and not fail_data_hash: + # Create a valid data blob hash. + op.data_sha256_hash = hashlib.sha256(fake_data.encode('utf-8')).digest() + payload.ReadDataBlob.return_value = fake_data.encode('utf-8') elif fail_data_hash: # Create an invalid data blob hash. op.data_sha256_hash = hashlib.sha256( - fake_data.replace(' ', '-')).digest() - payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn( - fake_data) + fake_data.replace(' ', '-').encode('utf-8')).digest() + payload.ReadDataBlob.return_value = fake_data.encode('utf-8') total_dst_blocks = 0 if not fail_missing_dst_extents: @@ -944,8 +837,7 @@ class PayloadCheckerTest(mox.MoxTestBase): if total_src_blocks: if fail_src_length: op.src_length = total_src_blocks * block_size + 8 - elif (op_type in (common.OpType.MOVE, common.OpType.BSDIFF, - common.OpType.SOURCE_BSDIFF) and + elif (op_type == common.OpType.SOURCE_BSDIFF and payload_checker.minor_version <= 3): op.src_length = total_src_blocks * block_size elif fail_src_length: @@ -955,19 +847,17 @@ class PayloadCheckerTest(mox.MoxTestBase): if total_dst_blocks: if fail_dst_length: op.dst_length = total_dst_blocks * block_size + 8 - elif (op_type in (common.OpType.MOVE, common.OpType.BSDIFF, - common.OpType.SOURCE_BSDIFF) and + elif (op_type == common.OpType.SOURCE_BSDIFF and payload_checker.minor_version <= 3): op.dst_length = total_dst_blocks * block_size - self.mox.ReplayAll() should_fail = (fail_src_extents or fail_dst_extents or fail_mismatched_data_offset_length or fail_missing_dst_extents or fail_src_length or fail_dst_length or fail_data_hash or fail_prev_data_offset or fail_bad_minor_version) - args = (op, 'foo', is_last, old_block_counters, new_block_counters, - old_part_size, new_part_size, prev_data_offset, allow_signature, + args = (op, 'foo', old_block_counters, new_block_counters, + old_part_size, new_part_size, prev_data_offset, blob_hash_counts) if should_fail: self.assertRaises(PayloadError, payload_checker._CheckOperation, *args) @@ -1009,8 +899,9 @@ class PayloadCheckerTest(mox.MoxTestBase): if fail_nonexhaustive_full_update: rootfs_data_length -= block_size - payload_gen.AddOperation(False, rootfs_op_type, - dst_extents=[(0, rootfs_data_length / block_size)], + payload_gen.AddOperation(common.ROOTFS, rootfs_op_type, + dst_extents= + [(0, rootfs_data_length // block_size)], data_offset=0, data_length=rootfs_data_length) @@ -1020,17 +911,17 @@ class PayloadCheckerTest(mox.MoxTestBase): 'allow_unhashed': True}) payload_checker.payload_type = checker._TYPE_FULL report = checker._PayloadReport() - - args = (payload_checker.payload.manifest.install_operations, report, 'foo', - 0, rootfs_part_size, rootfs_part_size, rootfs_part_size, 0, False) + partition = next((p for p in payload_checker.payload.manifest.partitions + if p.partition_name == common.ROOTFS), None) + args = (partition.operations, report, 'foo', + 0, rootfs_part_size, rootfs_part_size, rootfs_part_size, 0) if fail_nonexhaustive_full_update: self.assertRaises(PayloadError, payload_checker._CheckOperations, *args) else: self.assertEqual(rootfs_data_length, payload_checker._CheckOperations(*args)) - def DoCheckSignaturesTest(self, fail_empty_sigs_blob, fail_missing_pseudo_op, - fail_mismatched_pseudo_op, fail_sig_missing_fields, + def DoCheckSignaturesTest(self, fail_empty_sigs_blob, fail_sig_missing_fields, fail_unknown_sig_version, fail_incorrect_sig): """Tests _CheckSignatures().""" # Generate a test payload. For this test, we only care about the signature @@ -1041,20 +932,18 @@ class PayloadCheckerTest(mox.MoxTestBase): payload_gen.SetBlockSize(block_size) rootfs_part_size = test_utils.MiB(2) kernel_part_size = test_utils.KiB(16) - payload_gen.SetPartInfo(False, True, rootfs_part_size, - hashlib.sha256('fake-new-rootfs-content').digest()) - payload_gen.SetPartInfo(True, True, kernel_part_size, - hashlib.sha256('fake-new-kernel-content').digest()) + payload_gen.SetPartInfo(common.ROOTFS, True, rootfs_part_size, + hashlib.sha256(b'fake-new-rootfs-content').digest()) + payload_gen.SetPartInfo(common.KERNEL, True, kernel_part_size, + hashlib.sha256(b'fake-new-kernel-content').digest()) payload_gen.SetMinorVersion(0) payload_gen.AddOperationWithData( - False, common.OpType.REPLACE, - dst_extents=[(0, rootfs_part_size / block_size)], + common.ROOTFS, common.OpType.REPLACE, + dst_extents=[(0, rootfs_part_size // block_size)], data_blob=os.urandom(rootfs_part_size)) - do_forge_pseudo_op = (fail_missing_pseudo_op or fail_mismatched_pseudo_op) - do_forge_sigs_data = (do_forge_pseudo_op or fail_empty_sigs_blob or - fail_sig_missing_fields or fail_unknown_sig_version - or fail_incorrect_sig) + do_forge_sigs_data = (fail_empty_sigs_blob or fail_sig_missing_fields or + fail_unknown_sig_version or fail_incorrect_sig) sigs_data = None if do_forge_sigs_data: @@ -1063,29 +952,19 @@ class PayloadCheckerTest(mox.MoxTestBase): if fail_sig_missing_fields: sig_data = None else: - sig_data = test_utils.SignSha256('fake-payload-content', + sig_data = test_utils.SignSha256(b'fake-payload-content', test_utils._PRIVKEY_FILE_NAME) sigs_gen.AddSig(5 if fail_unknown_sig_version else 1, sig_data) sigs_data = sigs_gen.ToBinary() payload_gen.SetSignatures(payload_gen.curr_offset, len(sigs_data)) - if do_forge_pseudo_op: - assert sigs_data is not None, 'should have forged signatures blob by now' - sigs_len = len(sigs_data) - payload_gen.AddOperation( - False, common.OpType.REPLACE, - data_offset=payload_gen.curr_offset / 2, - data_length=sigs_len / 2, - dst_extents=[(0, (sigs_len / 2 + block_size - 1) / block_size)]) - # Generate payload (complete w/ signature) and create the test object. payload_checker = _GetPayloadChecker( payload_gen.WriteToFileWithData, payload_gen_dargs={ 'sigs_data': sigs_data, - 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME, - 'do_add_pseudo_operation': not do_forge_pseudo_op}) + 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME}) payload_checker.payload_type = checker._TYPE_FULL report = checker._PayloadReport() @@ -1095,8 +974,7 @@ class PayloadCheckerTest(mox.MoxTestBase): common.KERNEL: kernel_part_size }) - should_fail = (fail_empty_sigs_blob or fail_missing_pseudo_op or - fail_mismatched_pseudo_op or fail_sig_missing_fields or + should_fail = (fail_empty_sigs_blob or fail_sig_missing_fields or fail_unknown_sig_version or fail_incorrect_sig) args = (report, test_utils._PUBKEY_FILE_NAME) if should_fail: @@ -1120,7 +998,6 @@ class PayloadCheckerTest(mox.MoxTestBase): should_succeed = ( (minor_version == 0 and payload_type == checker._TYPE_FULL) or - (minor_version == 1 and payload_type == checker._TYPE_DELTA) or (minor_version == 2 and payload_type == checker._TYPE_DELTA) or (minor_version == 3 and payload_type == checker._TYPE_DELTA) or (minor_version == 4 and payload_type == checker._TYPE_DELTA) or @@ -1150,10 +1027,10 @@ class PayloadCheckerTest(mox.MoxTestBase): payload_gen.SetBlockSize(block_size) kernel_filesystem_size = test_utils.KiB(16) rootfs_filesystem_size = test_utils.MiB(2) - payload_gen.SetPartInfo(False, True, rootfs_filesystem_size, - hashlib.sha256('fake-new-rootfs-content').digest()) - payload_gen.SetPartInfo(True, True, kernel_filesystem_size, - hashlib.sha256('fake-new-kernel-content').digest()) + payload_gen.SetPartInfo(common.ROOTFS, True, rootfs_filesystem_size, + hashlib.sha256(b'fake-new-rootfs-content').digest()) + payload_gen.SetPartInfo(common.KERNEL, True, kernel_filesystem_size, + hashlib.sha256(b'fake-new-kernel-content').digest()) payload_gen.SetMinorVersion(0) rootfs_part_size = 0 @@ -1163,8 +1040,8 @@ class PayloadCheckerTest(mox.MoxTestBase): if fail_rootfs_part_size_exceeded: rootfs_op_size += block_size payload_gen.AddOperationWithData( - False, common.OpType.REPLACE, - dst_extents=[(0, rootfs_op_size / block_size)], + common.ROOTFS, common.OpType.REPLACE, + dst_extents=[(0, rootfs_op_size // block_size)], data_blob=os.urandom(rootfs_op_size)) kernel_part_size = 0 @@ -1174,8 +1051,8 @@ class PayloadCheckerTest(mox.MoxTestBase): if fail_kernel_part_size_exceeded: kernel_op_size += block_size payload_gen.AddOperationWithData( - True, common.OpType.REPLACE, - dst_extents=[(0, kernel_op_size / block_size)], + common.KERNEL, common.OpType.REPLACE, + dst_extents=[(0, kernel_op_size // block_size)], data_blob=os.urandom(kernel_op_size)) # Generate payload (complete w/ signature) and create the test object. @@ -1186,16 +1063,14 @@ class PayloadCheckerTest(mox.MoxTestBase): else: use_block_size = block_size - # For the unittests 246 is the value that generated for the payload. - metadata_size = 246 + # For the unittests 237 is the value that generated for the payload. + metadata_size = 237 if fail_mismatched_metadata_size: metadata_size += 1 kwargs = { 'payload_gen_dargs': { 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME, - 'do_add_pseudo_operation': True, - 'is_pseudo_in_kernel': True, 'padding': os.urandom(1024) if fail_excess_data else None}, 'checker_init_dargs': { 'assert_type': 'delta' if fail_wrong_payload_type else 'full', @@ -1207,7 +1082,7 @@ class PayloadCheckerTest(mox.MoxTestBase): payload_checker = _GetPayloadChecker(payload_gen.WriteToFileWithData, **kwargs) - kwargs = { + kwargs2 = { 'pubkey_file_name': test_utils._PUBKEY_FILE_NAME, 'metadata_size': metadata_size, 'part_sizes': { @@ -1219,15 +1094,15 @@ class PayloadCheckerTest(mox.MoxTestBase): fail_rootfs_part_size_exceeded or fail_kernel_part_size_exceeded) if should_fail: - self.assertRaises(PayloadError, payload_checker.Run, **kwargs) + self.assertRaises(PayloadError, payload_checker.Run, **kwargs2) else: - self.assertIsNone(payload_checker.Run(**kwargs)) + self.assertIsNone(payload_checker.Run(**kwargs2)) + # This implements a generic API, hence the occasional unused args. # pylint: disable=W0613 -def ValidateCheckOperationTest(op_type_name, is_last, allow_signature, - allow_unhashed, fail_src_extents, - fail_dst_extents, +def ValidateCheckOperationTest(op_type_name, allow_unhashed, + fail_src_extents, fail_dst_extents, fail_mismatched_data_offset_length, fail_missing_dst_extents, fail_src_length, fail_dst_length, fail_data_hash, @@ -1244,8 +1119,8 @@ def ValidateCheckOperationTest(op_type_name, is_last, allow_signature, fail_bad_minor_version)): return False - # MOVE and SOURCE_COPY operations don't carry data. - if (op_type in (common.OpType.MOVE, common.OpType.SOURCE_COPY) and ( + # SOURCE_COPY operation does not carry data. + if (op_type == common.OpType.SOURCE_COPY and ( fail_mismatched_data_offset_length or fail_data_hash or fail_prev_data_offset)): return False @@ -1274,14 +1149,14 @@ def AddParametricTests(tested_method_name, arg_space, validate_func=None): (values) associated with them. validate_func: A function used for validating test argument combinations. """ - for value_tuple in itertools.product(*arg_space.itervalues()): - run_dargs = dict(zip(arg_space.iterkeys(), value_tuple)) + for value_tuple in itertools.product(*iter(arg_space.values())): + run_dargs = dict(zip(iter(arg_space.keys()), value_tuple)) if validate_func and not validate_func(**run_dargs): continue run_method_name = 'Do%sTest' % tested_method_name test_method_name = 'test%s' % tested_method_name - for arg_key, arg_val in run_dargs.iteritems(): - if arg_val or type(arg_val) is int: + for arg_key, arg_val in run_dargs.items(): + if arg_val or isinstance(arg_val, int): test_method_name += '__%s=%s' % (arg_key, arg_val) setattr(PayloadCheckerTest, test_method_name, TestMethodBody(run_method_name, run_dargs)) @@ -1328,11 +1203,8 @@ def AddAllParametricTests(): # Add all _CheckOperation() test cases. AddParametricTests('CheckOperation', {'op_type_name': ('REPLACE', 'REPLACE_BZ', 'REPLACE_XZ', - 'MOVE', 'BSDIFF', 'SOURCE_COPY', - 'SOURCE_BSDIFF', 'PUFFDIFF', - 'BROTLI_BSDIFF'), - 'is_last': (True, False), - 'allow_signature': (True, False), + 'SOURCE_COPY', 'SOURCE_BSDIFF', + 'PUFFDIFF', 'BROTLI_BSDIFF'), 'allow_unhashed': (True, False), 'fail_src_extents': (True, False), 'fail_dst_extents': (True, False), @@ -1352,15 +1224,13 @@ def AddAllParametricTests(): # Add all _CheckOperations() test cases. AddParametricTests('CheckSignatures', {'fail_empty_sigs_blob': (True, False), - 'fail_missing_pseudo_op': (True, False), - 'fail_mismatched_pseudo_op': (True, False), 'fail_sig_missing_fields': (True, False), 'fail_unknown_sig_version': (True, False), 'fail_incorrect_sig': (True, False)}) # Add all _CheckManifestMinorVersion() test cases. AddParametricTests('CheckManifestMinorVersion', - {'minor_version': (None, 0, 1, 2, 3, 4, 5, 555), + {'minor_version': (None, 0, 2, 3, 4, 5, 555), 'payload_type': (checker._TYPE_FULL, checker._TYPE_DELTA)}) diff --git a/scripts/update_payload/common.py b/scripts/update_payload/common.py index 9061a754..b934cf88 100644 --- a/scripts/update_payload/common.py +++ b/scripts/update_payload/common.py @@ -16,8 +16,11 @@ """Utilities for update payload processing.""" +from __future__ import absolute_import from __future__ import print_function +import base64 + from update_payload import update_metadata_pb2 from update_payload.error import PayloadError @@ -25,18 +28,14 @@ from update_payload.error import PayloadError # # Constants. # -PSEUDO_EXTENT_MARKER = (1L << 64) - 1 # UINT64_MAX - SIG_ASN1_HEADER = ( - '\x30\x31\x30\x0d\x06\x09\x60\x86' - '\x48\x01\x65\x03\x04\x02\x01\x05' - '\x00\x04\x20' + b'\x30\x31\x30\x0d\x06\x09\x60\x86' + b'\x48\x01\x65\x03\x04\x02\x01\x05' + b'\x00\x04\x20' ) -CHROMEOS_MAJOR_PAYLOAD_VERSION = 1 BRILLO_MAJOR_PAYLOAD_VERSION = 2 -INPLACE_MINOR_PAYLOAD_VERSION = 1 SOURCE_MINOR_PAYLOAD_VERSION = 2 OPSRCHASH_MINOR_PAYLOAD_VERSION = 3 BROTLI_BSDIFF_MINOR_PAYLOAD_VERSION = 4 @@ -47,6 +46,7 @@ ROOTFS = 'root' # Tuple of (name in system, name in protobuf). CROS_PARTITIONS = ((KERNEL, KERNEL), (ROOTFS, 'rootfs')) + # # Payload operation types. # @@ -55,8 +55,6 @@ class OpType(object): _CLASS = update_metadata_pb2.InstallOperation REPLACE = _CLASS.REPLACE REPLACE_BZ = _CLASS.REPLACE_BZ - MOVE = _CLASS.MOVE - BSDIFF = _CLASS.BSDIFF SOURCE_COPY = _CLASS.SOURCE_COPY SOURCE_BSDIFF = _CLASS.SOURCE_BSDIFF ZERO = _CLASS.ZERO @@ -64,13 +62,11 @@ class OpType(object): REPLACE_XZ = _CLASS.REPLACE_XZ PUFFDIFF = _CLASS.PUFFDIFF BROTLI_BSDIFF = _CLASS.BROTLI_BSDIFF - ALL = (REPLACE, REPLACE_BZ, MOVE, BSDIFF, SOURCE_COPY, SOURCE_BSDIFF, ZERO, + ALL = (REPLACE, REPLACE_BZ, SOURCE_COPY, SOURCE_BSDIFF, ZERO, DISCARD, REPLACE_XZ, PUFFDIFF, BROTLI_BSDIFF) NAMES = { REPLACE: 'REPLACE', REPLACE_BZ: 'REPLACE_BZ', - MOVE: 'MOVE', - BSDIFF: 'BSDIFF', SOURCE_COPY: 'SOURCE_COPY', SOURCE_BSDIFF: 'SOURCE_BSDIFF', ZERO: 'ZERO', @@ -146,7 +142,7 @@ def Read(file_obj, length, offset=None, hasher=None): try: data = file_obj.read(length) - except IOError, e: + except IOError as e: raise PayloadError('error reading from file (%s): %s' % (file_obj.name, e)) if len(data) != length: @@ -167,13 +163,12 @@ def FormatExtent(ex, block_size=0): end_block = ex.start_block + ex.num_blocks if block_size: return '%d->%d * %d' % (ex.start_block, end_block, block_size) - else: - return '%d->%d' % (ex.start_block, end_block) + return '%d->%d' % (ex.start_block, end_block) def FormatSha256(digest): """Returns a canonical string representation of a SHA256 digest.""" - return digest.encode('base64').strip() + return base64.b64encode(digest).decode('utf-8') # diff --git a/scripts/update_payload/format_utils.py b/scripts/update_payload/format_utils.py index 6248ba9b..e73badf3 100644 --- a/scripts/update_payload/format_utils.py +++ b/scripts/update_payload/format_utils.py @@ -16,6 +16,8 @@ """Various formatting functions.""" +from __future__ import division + def NumToPercent(num, total, min_precision=1, max_precision=5): """Returns the percentage (string) of |num| out of |total|. @@ -50,7 +52,7 @@ def NumToPercent(num, total, min_precision=1, max_precision=5): precision = min(min_precision, max_precision) factor = 10 ** precision while precision <= max_precision: - percent = num * 100 * factor / total + percent = num * 100 * factor // total if percent: break factor *= 10 @@ -102,8 +104,8 @@ def BytesToHumanReadable(size, precision=1, decimal=False): magnitude = next_magnitude if exp != 0: - whole = size / magnitude - frac = (size % magnitude) * (10 ** precision) / magnitude + whole = size // magnitude + frac = (size % magnitude) * (10 ** precision) // magnitude while frac and not frac % 10: frac /= 10 return '%d%s %s' % (whole, '.%d' % frac if frac else '', suffixes[exp - 1]) diff --git a/scripts/update_payload/format_utils_unittest.py b/scripts/update_payload/format_utils_unittest.py index 42ea621c..4dcd6527 100755 --- a/scripts/update_payload/format_utils_unittest.py +++ b/scripts/update_payload/format_utils_unittest.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/env python # # Copyright (C) 2013 The Android Open Source Project # @@ -17,6 +17,11 @@ """Unit tests for format_utils.py.""" +# Disable check for function names to avoid errors based on old code +# pylint: disable-msg=invalid-name + +from __future__ import absolute_import + import unittest from update_payload import format_utils diff --git a/scripts/update_payload/histogram.py b/scripts/update_payload/histogram.py index 1ac2ab5d..bad2dc37 100644 --- a/scripts/update_payload/histogram.py +++ b/scripts/update_payload/histogram.py @@ -16,6 +16,9 @@ """Histogram generation tools.""" +from __future__ import absolute_import +from __future__ import division + from collections import defaultdict from update_payload import format_utils @@ -110,7 +113,7 @@ class Histogram(object): hist_bar = '|' for key, count in self.data: if self.total: - bar_len = count * self.scale / self.total + bar_len = count * self.scale // self.total hist_bar = '|%s|' % ('#' * bar_len).ljust(self.scale) line = '%s %s %s' % ( diff --git a/scripts/update_payload/histogram_unittest.py b/scripts/update_payload/histogram_unittest.py index e757dd02..ccde2bb1 100755 --- a/scripts/update_payload/histogram_unittest.py +++ b/scripts/update_payload/histogram_unittest.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2 +#!/usr/bin/env python # # Copyright (C) 2013 The Android Open Source Project # @@ -17,6 +17,11 @@ """Unit tests for histogram.py.""" +# Disable check for function names to avoid errors based on old code +# pylint: disable-msg=invalid-name + +from __future__ import absolute_import + import unittest from update_payload import format_utils diff --git a/scripts/update_payload/payload.py b/scripts/update_payload/payload.py index 2a0cb58d..998703ad 100644 --- a/scripts/update_payload/payload.py +++ b/scripts/update_payload/payload.py @@ -16,10 +16,14 @@ """Tools for reading, verifying and applying Chrome OS update payloads.""" +from __future__ import absolute_import from __future__ import print_function import hashlib +import io +import mmap import struct +import zipfile from update_payload import applier from update_payload import checker @@ -64,7 +68,7 @@ class Payload(object): """Update payload header struct.""" # Header constants; sizes are in bytes. - _MAGIC = 'CrAU' + _MAGIC = b'CrAU' _VERSION_SIZE = 8 _MANIFEST_LEN_SIZE = 8 _METADATA_SIGNATURE_LEN_SIZE = 4 @@ -111,7 +115,6 @@ class Payload(object): payload_file, self._METADATA_SIGNATURE_LEN_SIZE, True, hasher=hasher) - def __init__(self, payload_file, payload_file_offset=0): """Initialize the payload object. @@ -119,7 +122,15 @@ class Payload(object): payload_file: update payload file object open for reading payload_file_offset: the offset of the actual payload """ - self.payload_file = payload_file + if zipfile.is_zipfile(payload_file): + with zipfile.ZipFile(payload_file) as zfp: + self.payload_file = zfp.open("payload.bin", "r") + elif isinstance(payload_file, str): + payload_fp = open(payload_file, "rb") + payload_bytes = mmap.mmap(payload_fp.fileno(), 0, access=mmap.ACCESS_READ) + self.payload_file = io.BytesIO(payload_bytes) + else: + self.payload_file = payload_file self.payload_file_offset = payload_file_offset self.manifest_hasher = None self.is_init = False @@ -226,31 +237,6 @@ class Payload(object): self.is_init = True - def Describe(self): - """Emits the payload embedded description data to standard output.""" - def _DescribeImageInfo(description, image_info): - """Display info about the image.""" - def _DisplayIndentedValue(name, value): - print(' {:<14} {}'.format(name+':', value)) - - print('%s:' % description) - _DisplayIndentedValue('Channel', image_info.channel) - _DisplayIndentedValue('Board', image_info.board) - _DisplayIndentedValue('Version', image_info.version) - _DisplayIndentedValue('Key', image_info.key) - - if image_info.build_channel != image_info.channel: - _DisplayIndentedValue('Build channel', image_info.build_channel) - - if image_info.build_version != image_info.version: - _DisplayIndentedValue('Build version', image_info.build_version) - - if self.manifest.HasField('old_image_info'): - _DescribeImageInfo('Old Image', self.manifest.old_image_info) - - if self.manifest.HasField('new_image_info'): - _DescribeImageInfo('New Image', self.manifest.new_image_info) - def _AssertInit(self): """Raises an exception if the object was not initialized.""" if not self.is_init: @@ -263,9 +249,7 @@ class Payload(object): def IsDelta(self): """Returns True iff the payload appears to be a delta.""" self._AssertInit() - return (self.manifest.HasField('old_kernel_info') or - self.manifest.HasField('old_rootfs_info') or - any(partition.HasField('old_partition_info') + return (any(partition.HasField('old_partition_info') for partition in self.manifest.partitions)) def IsFull(self): diff --git a/scripts/update_payload/test_utils.py b/scripts/update_payload/test_utils.py index 1e2259d4..e153669e 100644 --- a/scripts/update_payload/test_utils.py +++ b/scripts/update_payload/test_utils.py @@ -16,9 +16,10 @@ """Utilities for unit testing.""" +from __future__ import absolute_import from __future__ import print_function -import cStringIO +import io import hashlib import os import struct @@ -70,7 +71,7 @@ def _WriteInt(file_obj, size, is_unsigned, val): """ try: file_obj.write(struct.pack(common.IntPackingFmtStr(size, is_unsigned), val)) - except IOError, e: + except IOError as e: raise payload.PayloadError('error writing to file (%s): %s' % (file_obj.name, e)) @@ -173,31 +174,37 @@ class PayloadGenerator(object): self.block_size = block_size _SetMsgField(self.manifest, 'block_size', block_size) - def SetPartInfo(self, is_kernel, is_new, part_size, part_hash): + def SetPartInfo(self, part_name, is_new, part_size, part_hash): """Set the partition info entry. Args: - is_kernel: whether this is kernel partition info - is_new: whether to set old (False) or new (True) info - part_size: the partition size (in fact, filesystem size) - part_hash: the partition hash + part_name: The name of the partition. + is_new: Whether to set old (False) or new (True) info. + part_size: The partition size (in fact, filesystem size). + part_hash: The partition hash. """ - if is_kernel: - part_info = (self.manifest.new_kernel_info if is_new - else self.manifest.old_kernel_info) - else: - part_info = (self.manifest.new_rootfs_info if is_new - else self.manifest.old_rootfs_info) + partition = next((x for x in self.manifest.partitions + if x.partition_name == part_name), None) + if partition is None: + partition = self.manifest.partitions.add() + partition.partition_name = part_name + + part_info = (partition.new_partition_info if is_new + else partition.old_partition_info) _SetMsgField(part_info, 'size', part_size) _SetMsgField(part_info, 'hash', part_hash) - def AddOperation(self, is_kernel, op_type, data_offset=None, + def AddOperation(self, part_name, op_type, data_offset=None, data_length=None, src_extents=None, src_length=None, dst_extents=None, dst_length=None, data_sha256_hash=None): """Adds an InstallOperation entry.""" - operations = (self.manifest.kernel_install_operations if is_kernel - else self.manifest.install_operations) + partition = next((x for x in self.manifest.partitions + if x.partition_name == part_name), None) + if partition is None: + partition = self.manifest.partitions.add() + partition.partition_name = part_name + operations = partition.operations op = operations.add() op.type = op_type @@ -277,7 +284,7 @@ class EnhancedPayloadGenerator(PayloadGenerator): self.data_blobs.append(data_blob) return data_length, data_offset - def AddOperationWithData(self, is_kernel, op_type, src_extents=None, + def AddOperationWithData(self, part_name, op_type, src_extents=None, src_length=None, dst_extents=None, dst_length=None, data_blob=None, do_hash_data_blob=True): """Adds an install operation and associated data blob. @@ -287,12 +294,12 @@ class EnhancedPayloadGenerator(PayloadGenerator): necessary offset/length accounting. Args: - is_kernel: whether this is a kernel (True) or rootfs (False) operation - op_type: one of REPLACE, REPLACE_BZ, REPLACE_XZ, MOVE or BSDIFF + part_name: The name of the partition (e.g. kernel or root). + op_type: one of REPLACE, REPLACE_BZ, REPLACE_XZ. src_extents: list of (start, length) pairs indicating src block ranges - src_length: size of the src data in bytes (needed for BSDIFF) + src_length: size of the src data in bytes (needed for diff operations) dst_extents: list of (start, length) pairs indicating dst block ranges - dst_length: size of the dst data in bytes (needed for BSDIFF) + dst_length: size of the dst data in bytes (needed for diff operations) data_blob: a data blob associated with this operation do_hash_data_blob: whether or not to compute and add a data blob hash """ @@ -302,15 +309,13 @@ class EnhancedPayloadGenerator(PayloadGenerator): data_sha256_hash = hashlib.sha256(data_blob).digest() data_length, data_offset = self.AddData(data_blob) - self.AddOperation(is_kernel, op_type, data_offset=data_offset, + self.AddOperation(part_name, op_type, data_offset=data_offset, data_length=data_length, src_extents=src_extents, src_length=src_length, dst_extents=dst_extents, dst_length=dst_length, data_sha256_hash=data_sha256_hash) def WriteToFileWithData(self, file_obj, sigs_data=None, - privkey_file_name=None, - do_add_pseudo_operation=False, - is_pseudo_in_kernel=False, padding=None): + privkey_file_name=None, padding=None): """Writes the payload content to a file, optionally signing the content. Args: @@ -319,10 +324,6 @@ class EnhancedPayloadGenerator(PayloadGenerator): payload signature fields assumed to be preset by the caller) privkey_file_name: key used for signing the payload (optional; used only if explicit signatures blob not provided) - do_add_pseudo_operation: whether a pseudo-operation should be added to - account for the signature blob - is_pseudo_in_kernel: whether the pseudo-operation should be added to - kernel (True) or rootfs (False) operations padding: stuff to dump past the normal data blobs provided (optional) Raises: @@ -335,7 +336,7 @@ class EnhancedPayloadGenerator(PayloadGenerator): if do_generate_sigs_data: # First, sign some arbitrary data to obtain the size of a signature blob. - fake_sig = SignSha256('fake-payload-data', privkey_file_name) + fake_sig = SignSha256(b'fake-payload-data', privkey_file_name) fake_sigs_gen = SignaturesGenerator() fake_sigs_gen.AddSig(1, fake_sig) sigs_len = len(fake_sigs_gen.ToBinary()) @@ -343,20 +344,9 @@ class EnhancedPayloadGenerator(PayloadGenerator): # Update the payload with proper signature attributes. self.SetSignatures(self.curr_offset, sigs_len) - # Add a pseudo-operation to account for the signature blob, if requested. - if do_add_pseudo_operation: - if not self.block_size: - raise TestError('cannot add pseudo-operation without knowing the ' - 'payload block size') - self.AddOperation( - is_pseudo_in_kernel, common.OpType.REPLACE, - data_offset=self.curr_offset, data_length=sigs_len, - dst_extents=[(common.PSEUDO_EXTENT_MARKER, - (sigs_len + self.block_size - 1) / self.block_size)]) - if do_generate_sigs_data: # Once all payload fields are updated, dump and sign it. - temp_payload_file = cStringIO.StringIO() + temp_payload_file = io.BytesIO() self.WriteToFile(temp_payload_file, data_blobs=self.data_blobs) sig = SignSha256(temp_payload_file.getvalue(), privkey_file_name) sigs_gen = SignaturesGenerator() diff --git a/scripts/update_payload/update_metadata_pb2.py b/scripts/update_payload/update_metadata_pb2.py index cb8f4c22..9aef9f2f 100644 --- a/scripts/update_payload/update_metadata_pb2.py +++ b/scripts/update_payload/update_metadata_pb2.py @@ -20,7 +20,7 @@ DESCRIPTOR = _descriptor.FileDescriptor( 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\"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\"\xb1\x06\n\x14\x44\x65ltaArchiveManifest\x12\x44\n\x12install_operations\x18\x01 \x03(\x0b\x32(.chromeos_update_engine.InstallOperation\x12K\n\x19kernel_install_operations\x18\x02 \x03(\x0b\x32(.chromeos_update_engine.InstallOperation\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>\n\x0fold_kernel_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12>\n\x0fnew_kernel_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12>\n\x0fold_rootfs_info\x18\x08 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12>\n\x0fnew_rootfs_info\x18\t \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\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_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\"\x8f\x01\n\tImageInfo\x12\x11\n\x05\x62oard\x18\x01 \x01(\tB\x02\x18\x01\x12\x0f\n\x03key\x18\x02 \x01(\tB\x02\x18\x01\x12\x13\n\x07\x63hannel\x18\x03 \x01(\tB\x02\x18\x01\x12\x13\n\x07version\x18\x04 \x01(\tB\x02\x18\x01\x12\x19\n\rbuild_channel\x18\x05 \x01(\tB\x02\x18\x01\x12\x19\n\rbuild_version\x18\x06 \x01(\tB\x02\x18\x01\"\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\"\xcf\x01\n\x11\x43owMergeOperation\x12<\n\x04type\x18\x01 \x01(\x0e\x32..chromeos_update_engine.CowMergeOperation.Type\x12\x32\n\nsrc_extent\x18\x02 \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x32\n\ndst_extent\x18\x03 \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\"\x14\n\x04Type\x12\x0c\n\x08\x43OW_COPY\x10\x00\"\xc8\x06\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\x12\x43\n\x10merge_operations\x18\x12 \x03(\x0b\x32).chromeos_update_engine.CowMergeOperation\x12\x19\n\x11\x65stimate_cow_size\x18\x13 \x01(\x04\"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\"\xa9\x01\n\x18\x44ynamicPartitionMetadata\x12=\n\x06groups\x18\x01 \x03(\x0b\x32-.chromeos_update_engine.DynamicPartitionGroup\x12\x18\n\x10snapshot_enabled\x18\x02 \x01(\x08\x12\x14\n\x0cvabc_enabled\x18\x03 \x01(\x08\x12\x1e\n\x16vabc_compression_param\x18\x04 \x01(\t\"c\n\x08\x41pexInfo\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\x03\x12\x15\n\ris_compressed\x18\x03 \x01(\x08\x12\x19\n\x11\x64\x65\x63ompressed_size\x18\x04 \x01(\x03\"C\n\x0c\x41pexMetadata\x12\x33\n\tapex_info\x18\x01 \x03(\x0b\x32 .chromeos_update_engine.ApexInfo\"\x9e\x07\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=\n\x0eold_image_info\x18\n \x01(\x0b\x32!.chromeos_update_engine.ImageInfoB\x02\x18\x01\x12=\n\x0enew_image_info\x18\x0b \x01(\x0b\x32!.chromeos_update_engine.ImageInfoB\x02\x18\x01\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\x12\x33\n\tapex_info\x18\x11 \x03(\x0b\x32 .chromeos_update_engine.ApexInfoB\x02H\x03') ) @@ -78,11 +78,29 @@ _INSTALLOPERATION_TYPE = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=712, - serialized_end=885, + serialized_start=775, + serialized_end=948, ) _sym_db.RegisterEnumDescriptor(_INSTALLOPERATION_TYPE) +_COWMERGEOPERATION_TYPE = _descriptor.EnumDescriptor( + name='Type', + full_name='chromeos_update_engine.CowMergeOperation.Type', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='COW_COPY', index=0, number=0, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=1138, + serialized_end=1158, +) +_sym_db.RegisterEnumDescriptor(_COWMERGEOPERATION_TYPE) + _EXTENT = _descriptor.Descriptor( name='Extent', @@ -135,7 +153,7 @@ _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, @@ -143,6 +161,13 @@ _SIGNATURES_SIGNATURE = _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='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), ], extensions=[ ], @@ -155,8 +180,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 +210,8 @@ _SIGNATURES = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=100, - serialized_end=222, + serialized_start=101, + serialized_end=260, ) @@ -223,8 +248,8 @@ _PARTITIONINFO = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=224, - serialized_end=267, + serialized_start=262, + serialized_end=305, ) @@ -241,42 +266,42 @@ _IMAGEINFO = _descriptor.Descriptor( 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), + serialized_options=_b('\030\001'), 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'), 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='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'), 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='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'), 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='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'), 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='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'), 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), ], extensions=[ ], @@ -289,8 +314,8 @@ _IMAGEINFO = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=269, - serialized_end=388, + serialized_start=308, + serialized_end=451, ) @@ -377,8 +402,54 @@ _INSTALLOPERATION = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=391, - serialized_end=885, + serialized_start=454, + serialized_end=948, +) + + +_COWMERGEOPERATION = _descriptor.Descriptor( + name='CowMergeOperation', + full_name='chromeos_update_engine.CowMergeOperation', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='type', full_name='chromeos_update_engine.CowMergeOperation.type', index=0, + number=1, type=14, cpp_type=8, 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), + _descriptor.FieldDescriptor( + name='src_extent', full_name='chromeos_update_engine.CowMergeOperation.src_extent', index=1, + number=2, 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=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='dst_extent', full_name='chromeos_update_engine.CowMergeOperation.dst_extent', index=2, + number=3, 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=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _COWMERGEOPERATION_TYPE, + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=951, + serialized_end=1158, ) @@ -501,6 +572,27 @@ _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), + _descriptor.FieldDescriptor( + name='merge_operations', full_name='chromeos_update_engine.PartitionUpdate.merge_operations', index=17, + number=18, 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=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='estimate_cow_size', full_name='chromeos_update_engine.PartitionUpdate.estimate_cow_size', index=18, + number=19, type=4, cpp_type=4, 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), ], extensions=[ ], @@ -513,8 +605,8 @@ _PARTITIONUPDATE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=888, - serialized_end=1615, + serialized_start=1161, + serialized_end=2001, ) @@ -558,8 +650,8 @@ _DYNAMICPARTITIONGROUP = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1617, - serialized_end=1693, + serialized_start=2003, + serialized_end=2079, ) @@ -584,6 +676,103 @@ _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='vabc_enabled', full_name='chromeos_update_engine.DynamicPartitionMetadata.vabc_enabled', index=2, + number=3, 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), + _descriptor.FieldDescriptor( + name='vabc_compression_param', full_name='chromeos_update_engine.DynamicPartitionMetadata.vabc_compression_param', index=3, + number=4, 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=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2082, + serialized_end=2251, +) + + +_APEXINFO = _descriptor.Descriptor( + name='ApexInfo', + full_name='chromeos_update_engine.ApexInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='package_name', full_name='chromeos_update_engine.ApexInfo.package_name', index=0, + number=1, 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), + _descriptor.FieldDescriptor( + name='version', full_name='chromeos_update_engine.ApexInfo.version', index=1, + number=2, type=3, cpp_type=2, 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), + _descriptor.FieldDescriptor( + name='is_compressed', full_name='chromeos_update_engine.ApexInfo.is_compressed', index=2, + number=3, 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), + _descriptor.FieldDescriptor( + name='decompressed_size', full_name='chromeos_update_engine.ApexInfo.decompressed_size', index=3, + number=4, type=3, cpp_type=2, 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), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2253, + serialized_end=2352, +) + + +_APEXMETADATA = _descriptor.Descriptor( + name='ApexMetadata', + full_name='chromeos_update_engine.ApexMetadata', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='apex_info', full_name='chromeos_update_engine.ApexMetadata.apex_info', index=0, + number=1, 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=None, file=DESCRIPTOR), ], extensions=[ ], @@ -596,8 +785,8 @@ _DYNAMICPARTITIONMETADATA = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1695, - serialized_end=1810, + serialized_start=2354, + serialized_end=2421, ) @@ -614,14 +803,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=None, 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=None, 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, @@ -649,42 +838,42 @@ _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=None, 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=None, 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=None, 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=None, 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, has_default_value=False, default_value=None, 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='new_image_info', full_name='chromeos_update_engine.DeltaArchiveManifest.new_image_info', index=10, number=11, 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=None, file=DESCRIPTOR), + serialized_options=_b('\030\001'), file=DESCRIPTOR), _descriptor.FieldDescriptor( name='minor_version', full_name='chromeos_update_engine.DeltaArchiveManifest.minor_version', index=11, number=12, type=13, cpp_type=3, label=1, @@ -713,6 +902,20 @@ _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), + _descriptor.FieldDescriptor( + name='apex_info', full_name='chromeos_update_engine.DeltaArchiveManifest.apex_info', index=16, + number=17, 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=None, file=DESCRIPTOR), ], extensions=[ ], @@ -725,8 +928,8 @@ _DELTAARCHIVEMANIFEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1813, - serialized_end=2630, + serialized_start=2424, + serialized_end=3350, ) _SIGNATURES_SIGNATURE.containing_type = _SIGNATURES @@ -735,6 +938,10 @@ _INSTALLOPERATION.fields_by_name['type'].enum_type = _INSTALLOPERATION_TYPE _INSTALLOPERATION.fields_by_name['src_extents'].message_type = _EXTENT _INSTALLOPERATION.fields_by_name['dst_extents'].message_type = _EXTENT _INSTALLOPERATION_TYPE.containing_type = _INSTALLOPERATION +_COWMERGEOPERATION.fields_by_name['type'].enum_type = _COWMERGEOPERATION_TYPE +_COWMERGEOPERATION.fields_by_name['src_extent'].message_type = _EXTENT +_COWMERGEOPERATION.fields_by_name['dst_extent'].message_type = _EXTENT +_COWMERGEOPERATION_TYPE.containing_type = _COWMERGEOPERATION _PARTITIONUPDATE.fields_by_name['new_partition_signature'].message_type = _SIGNATURES_SIGNATURE _PARTITIONUPDATE.fields_by_name['old_partition_info'].message_type = _PARTITIONINFO _PARTITIONUPDATE.fields_by_name['new_partition_info'].message_type = _PARTITIONINFO @@ -743,7 +950,9 @@ _PARTITIONUPDATE.fields_by_name['hash_tree_data_extent'].message_type = _EXTENT _PARTITIONUPDATE.fields_by_name['hash_tree_extent'].message_type = _EXTENT _PARTITIONUPDATE.fields_by_name['fec_data_extent'].message_type = _EXTENT _PARTITIONUPDATE.fields_by_name['fec_extent'].message_type = _EXTENT +_PARTITIONUPDATE.fields_by_name['merge_operations'].message_type = _COWMERGEOPERATION _DYNAMICPARTITIONMETADATA.fields_by_name['groups'].message_type = _DYNAMICPARTITIONGROUP +_APEXMETADATA.fields_by_name['apex_info'].message_type = _APEXINFO _DELTAARCHIVEMANIFEST.fields_by_name['install_operations'].message_type = _INSTALLOPERATION _DELTAARCHIVEMANIFEST.fields_by_name['kernel_install_operations'].message_type = _INSTALLOPERATION _DELTAARCHIVEMANIFEST.fields_by_name['old_kernel_info'].message_type = _PARTITIONINFO @@ -754,14 +963,18 @@ _DELTAARCHIVEMANIFEST.fields_by_name['old_image_info'].message_type = _IMAGEINFO _DELTAARCHIVEMANIFEST.fields_by_name['new_image_info'].message_type = _IMAGEINFO _DELTAARCHIVEMANIFEST.fields_by_name['partitions'].message_type = _PARTITIONUPDATE _DELTAARCHIVEMANIFEST.fields_by_name['dynamic_partition_metadata'].message_type = _DYNAMICPARTITIONMETADATA +_DELTAARCHIVEMANIFEST.fields_by_name['apex_info'].message_type = _APEXINFO DESCRIPTOR.message_types_by_name['Extent'] = _EXTENT DESCRIPTOR.message_types_by_name['Signatures'] = _SIGNATURES DESCRIPTOR.message_types_by_name['PartitionInfo'] = _PARTITIONINFO DESCRIPTOR.message_types_by_name['ImageInfo'] = _IMAGEINFO DESCRIPTOR.message_types_by_name['InstallOperation'] = _INSTALLOPERATION +DESCRIPTOR.message_types_by_name['CowMergeOperation'] = _COWMERGEOPERATION DESCRIPTOR.message_types_by_name['PartitionUpdate'] = _PARTITIONUPDATE DESCRIPTOR.message_types_by_name['DynamicPartitionGroup'] = _DYNAMICPARTITIONGROUP DESCRIPTOR.message_types_by_name['DynamicPartitionMetadata'] = _DYNAMICPARTITIONMETADATA +DESCRIPTOR.message_types_by_name['ApexInfo'] = _APEXINFO +DESCRIPTOR.message_types_by_name['ApexMetadata'] = _APEXMETADATA DESCRIPTOR.message_types_by_name['DeltaArchiveManifest'] = _DELTAARCHIVEMANIFEST _sym_db.RegisterFileDescriptor(DESCRIPTOR) @@ -808,6 +1021,13 @@ InstallOperation = _reflection.GeneratedProtocolMessageType('InstallOperation', }) _sym_db.RegisterMessage(InstallOperation) +CowMergeOperation = _reflection.GeneratedProtocolMessageType('CowMergeOperation', (_message.Message,), { + 'DESCRIPTOR' : _COWMERGEOPERATION, + '__module__' : 'update_metadata_pb2' + # @@protoc_insertion_point(class_scope:chromeos_update_engine.CowMergeOperation) + }) +_sym_db.RegisterMessage(CowMergeOperation) + PartitionUpdate = _reflection.GeneratedProtocolMessageType('PartitionUpdate', (_message.Message,), { 'DESCRIPTOR' : _PARTITIONUPDATE, '__module__' : 'update_metadata_pb2' @@ -829,6 +1049,20 @@ DynamicPartitionMetadata = _reflection.GeneratedProtocolMessageType('DynamicPart }) _sym_db.RegisterMessage(DynamicPartitionMetadata) +ApexInfo = _reflection.GeneratedProtocolMessageType('ApexInfo', (_message.Message,), { + 'DESCRIPTOR' : _APEXINFO, + '__module__' : 'update_metadata_pb2' + # @@protoc_insertion_point(class_scope:chromeos_update_engine.ApexInfo) + }) +_sym_db.RegisterMessage(ApexInfo) + +ApexMetadata = _reflection.GeneratedProtocolMessageType('ApexMetadata', (_message.Message,), { + 'DESCRIPTOR' : _APEXMETADATA, + '__module__' : 'update_metadata_pb2' + # @@protoc_insertion_point(class_scope:chromeos_update_engine.ApexMetadata) + }) +_sym_db.RegisterMessage(ApexMetadata) + DeltaArchiveManifest = _reflection.GeneratedProtocolMessageType('DeltaArchiveManifest', (_message.Message,), { 'DESCRIPTOR' : _DELTAARCHIVEMANIFEST, '__module__' : 'update_metadata_pb2' @@ -838,6 +1072,21 @@ _sym_db.RegisterMessage(DeltaArchiveManifest) DESCRIPTOR._options = None +_SIGNATURES_SIGNATURE.fields_by_name['version']._options = None +_IMAGEINFO.fields_by_name['board']._options = None +_IMAGEINFO.fields_by_name['key']._options = None +_IMAGEINFO.fields_by_name['channel']._options = None +_IMAGEINFO.fields_by_name['version']._options = None +_IMAGEINFO.fields_by_name['build_channel']._options = None +_IMAGEINFO.fields_by_name['build_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 +_DELTAARCHIVEMANIFEST.fields_by_name['kernel_install_operations']._options = None +_DELTAARCHIVEMANIFEST.fields_by_name['old_kernel_info']._options = None +_DELTAARCHIVEMANIFEST.fields_by_name['new_kernel_info']._options = None +_DELTAARCHIVEMANIFEST.fields_by_name['old_rootfs_info']._options = None +_DELTAARCHIVEMANIFEST.fields_by_name['new_rootfs_info']._options = None +_DELTAARCHIVEMANIFEST.fields_by_name['old_image_info']._options = None +_DELTAARCHIVEMANIFEST.fields_by_name['new_image_info']._options = None # @@protoc_insertion_point(module_scope) diff --git a/stable/Android.bp b/stable/Android.bp new file mode 100644 index 00000000..1573ebd2 --- /dev/null +++ b/stable/Android.bp @@ -0,0 +1,79 @@ +// +// 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 +// ======================================================== +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "system_update_engine_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["system_update_engine_license"], +} + +aidl_interface { + name: "libupdate_engine_stable", + + // This header library is available to core and product modules. + product_available: true, + + srcs: [ + "android/os/IUpdateEngineStable.aidl", + "android/os/IUpdateEngineStableCallback.aidl", + ], + backend: { + cpp: { + enabled: true, + }, + java: { + enabled: false, + }, + ndk: { + enabled: true, + apex_available: [ + "com.android.gki.*", + ], + }, + }, + versions: ["1"], +} + +// update_engine_stable_client (type: executable) +// ======================================================== +// update_engine console client installed to APEXes. +cc_binary { + name: "update_engine_stable_client", + product_specific: true, + header_libs: [ + "libupdate_engine_headers", + ], + shared_libs: [ + "libbinder_ndk", + "libbase", + "liblog", + ], + static_libs: [ + "libgflags", + "libupdate_engine_stable-V1-ndk_platform", + ], + srcs: [ + "update_engine_stable_client.cc", + ], + apex_available: [ + "com.android.gki.*", + ], +} diff --git a/stable/aidl_api/libupdate_engine_stable/1/.hash b/stable/aidl_api/libupdate_engine_stable/1/.hash new file mode 100644 index 00000000..f21562a4 --- /dev/null +++ b/stable/aidl_api/libupdate_engine_stable/1/.hash @@ -0,0 +1 @@ +526043ea6cb098d53a9c3e778420e64c4e864d8c diff --git a/stable/aidl_api/libupdate_engine_stable/1/android/os/IUpdateEngineStable.aidl b/stable/aidl_api/libupdate_engine_stable/1/android/os/IUpdateEngineStable.aidl new file mode 100644 index 00000000..67db18e9 --- /dev/null +++ b/stable/aidl_api/libupdate_engine_stable/1/android/os/IUpdateEngineStable.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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file 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/1/android/os/IUpdateEngineStableCallback.aidl b/stable/aidl_api/libupdate_engine_stable/1/android/os/IUpdateEngineStableCallback.aidl new file mode 100644 index 00000000..dbca127c --- /dev/null +++ b/stable/aidl_api/libupdate_engine_stable/1/android/os/IUpdateEngineStableCallback.aidl @@ -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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file 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/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..17f66b67 --- /dev/null +++ b/stable/update_engine_stable_client.cc @@ -0,0 +1,187 @@ +// +// 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> + +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/tar_bunzip2.gni b/tar_bunzip2.gni new file mode 100644 index 00000000..5d901675 --- /dev/null +++ b/tar_bunzip2.gni @@ -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. +# + +template("tar_bunzip2") { + forward_variables_from(invoker, [ "image_out_dir" ]) + out_dir = "${root_gen_dir}/${image_out_dir}" + + action_foreach(target_name) { + sources = invoker.sources + script = "//common-mk/file_generator_wrapper.py" + outputs = [ "${out_dir}/{{source_name_part}}.flag" ] + args = [ + "sh", + "-c", + "tar -xvf \"{{source}}\" -C \"${out_dir}\" && touch ${out_dir}/{{source_name_part}}.flag", + ] + } +} diff --git a/tar_bunzip2.gypi b/tar_bunzip2.gypi deleted file mode 100644 index 4d1be284..00000000 --- a/tar_bunzip2.gypi +++ /dev/null @@ -1,39 +0,0 @@ -# -# Copyright (C) 2015 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. -# -{ - 'variables': { - 'out_dir': '<(SHARED_INTERMEDIATE_DIR)/<(image_out_dir)', - }, - 'rules': [ - { - 'rule_name': 'tar-bunzip2', - 'extension': 'bz2', - 'outputs': [ - # The .flag file is used to mark the timestamp of the file extraction - # and re-run this action if a new .bz2 file is generated. - '<(out_dir)/<(RULE_INPUT_ROOT).flag', - ], - 'action': [ - 'sh', - '-c', - 'tar -xvf "<(RULE_INPUT_PATH)" -C "<(out_dir)" && touch <(out_dir)/<(RULE_INPUT_ROOT).flag', - ], - 'msvs_cygwin_shell': 0, - 'process_outputs_as_sources': 1, - 'message': 'Unpacking file <(RULE_INPUT_PATH)', - }, - ], -} diff --git a/test_config.xml b/test_config.xml index 2639e7f3..fe3cbfda 100644 --- a/test_config.xml +++ b/test_config.xml @@ -16,13 +16,14 @@ <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/local/tmp/update_engine_unittests" /> + <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/local/tmp" /> + <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$" /> diff --git a/test_http_server.cc b/test_http_server.cc index 4536f372..a2f1e052 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); } @@ -658,5 +660,4 @@ int main(int argc, char** argv) { LOG(FATAL) << "ERROR on accept"; HandleConnection(client_fd); } - return 0; } diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc deleted file mode 100644 index 579c7368..00000000 --- a/update_attempter_unittest.cc +++ /dev/null @@ -1,1569 +0,0 @@ -// -// Copyright (C) 2012 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/update_attempter.h" - -#include <stdint.h> - -#include <memory> - -#include <base/files/file_util.h> -#include <base/message_loop/message_loop.h> -#include <brillo/message_loops/base_message_loop.h> -#include <brillo/message_loops/message_loop.h> -#include <brillo/message_loops/message_loop_utils.h> -#include <gtest/gtest.h> -#include <policy/libpolicy.h> -#include <policy/mock_device_policy.h> -#include <policy/mock_libpolicy.h> - -#include "update_engine/common/dlcservice_interface.h" -#include "update_engine/common/fake_clock.h" -#include "update_engine/common/fake_prefs.h" -#include "update_engine/common/mock_action.h" -#include "update_engine/common/mock_action_processor.h" -#include "update_engine/common/mock_http_fetcher.h" -#include "update_engine/common/mock_prefs.h" -#include "update_engine/common/platform_constants.h" -#include "update_engine/common/prefs.h" -#include "update_engine/common/test_utils.h" -#include "update_engine/common/utils.h" -#include "update_engine/fake_system_state.h" -#include "update_engine/mock_p2p_manager.h" -#include "update_engine/mock_payload_state.h" -#include "update_engine/mock_service_observer.h" -#include "update_engine/payload_consumer/filesystem_verifier_action.h" -#include "update_engine/payload_consumer/install_plan.h" -#include "update_engine/payload_consumer/payload_constants.h" -#include "update_engine/payload_consumer/postinstall_runner_action.h" -#include "update_engine/update_boot_flags_action.h" - -using base::Time; -using base::TimeDelta; -using chromeos_update_manager::EvalStatus; -using chromeos_update_manager::StagingSchedule; -using chromeos_update_manager::UpdateCheckParams; -using policy::DevicePolicy; -using std::string; -using std::unique_ptr; -using std::vector; -using testing::_; -using testing::DoAll; -using testing::Field; -using testing::InSequence; -using testing::Ne; -using testing::NiceMock; -using testing::Pointee; -using testing::Property; -using testing::Return; -using testing::ReturnPointee; -using testing::ReturnRef; -using testing::SaveArg; -using testing::SetArgPointee; -using update_engine::UpdateAttemptFlags; -using update_engine::UpdateEngineStatus; -using update_engine::UpdateStatus; - -namespace chromeos_update_engine { - -namespace { - -class MockDlcService : public DlcServiceInterface { - public: - MOCK_METHOD1(GetInstalled, bool(vector<string>*)); -}; - -} // namespace - -const char kRollbackVersion[] = "10575.39.2"; - -// Test a subclass rather than the main class directly so that we can mock out -// methods within the class. There're explicit unit tests for the mocked out -// methods. -class UpdateAttempterUnderTest : public UpdateAttempter { - public: - explicit UpdateAttempterUnderTest(SystemState* system_state) - : UpdateAttempter(system_state, nullptr) {} - - // Wrap the update scheduling method, allowing us to opt out of scheduled - // updates for testing purposes. - bool ScheduleUpdates() override { - schedule_updates_called_ = true; - if (do_schedule_updates_) { - UpdateAttempter::ScheduleUpdates(); - } else { - LOG(INFO) << "[TEST] Update scheduling disabled."; - } - return true; - } - void EnableScheduleUpdates() { do_schedule_updates_ = true; } - void DisableScheduleUpdates() { do_schedule_updates_ = false; } - - // Indicates whether ScheduleUpdates() was called. - bool schedule_updates_called() const { return schedule_updates_called_; } - - // Need to expose forced_omaha_url_ so we can test it. - const string& forced_omaha_url() const { return forced_omaha_url_; } - - private: - bool schedule_updates_called_ = false; - bool do_schedule_updates_ = true; -}; - -class UpdateAttempterTest : public ::testing::Test { - protected: - UpdateAttempterTest() - : certificate_checker_(fake_system_state_.mock_prefs(), - &openssl_wrapper_) { - // Override system state members. - fake_system_state_.set_connection_manager(&mock_connection_manager); - fake_system_state_.set_update_attempter(&attempter_); - fake_system_state_.set_dlcservice(&mock_dlcservice_); - loop_.SetAsCurrent(); - - certificate_checker_.Init(); - - attempter_.set_forced_update_pending_callback( - new base::Callback<void(bool, bool)>(base::Bind([](bool, bool) {}))); - // Finish initializing the attempter. - attempter_.Init(); - } - - void SetUp() override { - EXPECT_NE(nullptr, attempter_.system_state_); - EXPECT_EQ(0, attempter_.http_response_code_); - EXPECT_EQ(UpdateStatus::IDLE, attempter_.status_); - EXPECT_EQ(0.0, attempter_.download_progress_); - EXPECT_EQ(0, attempter_.last_checked_time_); - EXPECT_EQ("0.0.0.0", attempter_.new_version_); - EXPECT_EQ(0ULL, attempter_.new_payload_size_); - processor_ = new NiceMock<MockActionProcessor>(); - attempter_.processor_.reset(processor_); // Transfers ownership. - prefs_ = fake_system_state_.mock_prefs(); - - // Set up store/load semantics of P2P properties via the mock PayloadState. - actual_using_p2p_for_downloading_ = false; - EXPECT_CALL(*fake_system_state_.mock_payload_state(), - SetUsingP2PForDownloading(_)) - .WillRepeatedly(SaveArg<0>(&actual_using_p2p_for_downloading_)); - EXPECT_CALL(*fake_system_state_.mock_payload_state(), - GetUsingP2PForDownloading()) - .WillRepeatedly(ReturnPointee(&actual_using_p2p_for_downloading_)); - actual_using_p2p_for_sharing_ = false; - EXPECT_CALL(*fake_system_state_.mock_payload_state(), - SetUsingP2PForSharing(_)) - .WillRepeatedly(SaveArg<0>(&actual_using_p2p_for_sharing_)); - EXPECT_CALL(*fake_system_state_.mock_payload_state(), - GetUsingP2PForDownloading()) - .WillRepeatedly(ReturnPointee(&actual_using_p2p_for_sharing_)); - } - - public: - void ScheduleQuitMainLoop(); - - // Callbacks to run the different tests from the main loop. - void UpdateTestStart(); - void UpdateTestVerify(); - void RollbackTestStart(bool enterprise_rollback, bool valid_slot); - void RollbackTestVerify(); - void PingOmahaTestStart(); - void ReadScatterFactorFromPolicyTestStart(); - void DecrementUpdateCheckCountTestStart(); - void NoScatteringDoneDuringManualUpdateTestStart(); - void P2PNotEnabledStart(); - void P2PEnabledStart(); - void P2PEnabledInteractiveStart(); - void P2PEnabledStartingFailsStart(); - void P2PEnabledHousekeepingFailsStart(); - void ResetRollbackHappenedStart(bool is_consumer, - bool is_policy_available, - bool expected_reset); - // Staging related callbacks. - void SetUpStagingTest(const StagingSchedule& schedule, FakePrefs* prefs); - void CheckStagingOff(); - void StagingSetsPrefsAndTurnsOffScatteringStart(); - void StagingOffIfInteractiveStart(); - void StagingOffIfOobeStart(); - - bool actual_using_p2p_for_downloading() { - return actual_using_p2p_for_downloading_; - } - bool actual_using_p2p_for_sharing() { return actual_using_p2p_for_sharing_; } - - base::MessageLoopForIO base_loop_; - brillo::BaseMessageLoop loop_{&base_loop_}; - - FakeSystemState fake_system_state_; - UpdateAttempterUnderTest attempter_{&fake_system_state_}; - OpenSSLWrapper openssl_wrapper_; - CertificateChecker certificate_checker_; - MockDlcService mock_dlcservice_; - - NiceMock<MockActionProcessor>* processor_; - NiceMock<MockPrefs>* prefs_; // Shortcut to fake_system_state_->mock_prefs(). - NiceMock<MockConnectionManager> mock_connection_manager; - - bool actual_using_p2p_for_downloading_; - bool actual_using_p2p_for_sharing_; -}; - -void UpdateAttempterTest::ScheduleQuitMainLoop() { - loop_.PostTask( - FROM_HERE, - base::Bind([](brillo::BaseMessageLoop* loop) { loop->BreakLoop(); }, - base::Unretained(&loop_))); -} - -TEST_F(UpdateAttempterTest, ActionCompletedDownloadTest) { - unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr)); - fetcher->FailTransfer(503); // Sets the HTTP response code. - DownloadAction action(prefs_, - nullptr, - nullptr, - nullptr, - fetcher.release(), - false /* interactive */); - EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)).Times(0); - attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess); - EXPECT_EQ(UpdateStatus::FINALIZING, attempter_.status()); - EXPECT_EQ(0.0, attempter_.download_progress_); - ASSERT_EQ(nullptr, attempter_.error_event_.get()); -} - -TEST_F(UpdateAttempterTest, ActionCompletedErrorTest) { - MockAction action; - EXPECT_CALL(action, Type()).WillRepeatedly(Return("MockAction")); - attempter_.status_ = UpdateStatus::DOWNLOADING; - EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)) - .WillOnce(Return(false)); - attempter_.ActionCompleted(nullptr, &action, ErrorCode::kError); - ASSERT_NE(nullptr, attempter_.error_event_.get()); -} - -TEST_F(UpdateAttempterTest, DownloadProgressAccumulationTest) { - // Simple test case, where all the values match (nothing was skipped) - uint64_t bytes_progressed_1 = 1024 * 1024; // 1MB - uint64_t bytes_progressed_2 = 1024 * 1024; // 1MB - uint64_t bytes_received_1 = bytes_progressed_1; - uint64_t bytes_received_2 = bytes_received_1 + bytes_progressed_2; - uint64_t bytes_total = 20 * 1024 * 1024; // 20MB - - double progress_1 = - static_cast<double>(bytes_received_1) / static_cast<double>(bytes_total); - double progress_2 = - static_cast<double>(bytes_received_2) / static_cast<double>(bytes_total); - - EXPECT_EQ(0.0, attempter_.download_progress_); - // This is set via inspecting the InstallPlan payloads when the - // OmahaResponseAction is completed - attempter_.new_payload_size_ = bytes_total; - NiceMock<MockServiceObserver> observer; - EXPECT_CALL(observer, - SendStatusUpdate(AllOf( - Field(&UpdateEngineStatus::progress, progress_1), - Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING), - Field(&UpdateEngineStatus::new_size_bytes, bytes_total)))); - EXPECT_CALL(observer, - SendStatusUpdate(AllOf( - Field(&UpdateEngineStatus::progress, progress_2), - Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING), - Field(&UpdateEngineStatus::new_size_bytes, bytes_total)))); - attempter_.AddObserver(&observer); - attempter_.BytesReceived(bytes_progressed_1, bytes_received_1, bytes_total); - EXPECT_EQ(progress_1, attempter_.download_progress_); - // This iteration validates that a later set of updates to the variables are - // properly handled (so that |getStatus()| will return the same progress info - // as the callback is receiving. - attempter_.BytesReceived(bytes_progressed_2, bytes_received_2, bytes_total); - EXPECT_EQ(progress_2, attempter_.download_progress_); -} - -TEST_F(UpdateAttempterTest, ChangeToDownloadingOnReceivedBytesTest) { - // The transition into UpdateStatus::DOWNLOADING happens when the - // first bytes are received. - uint64_t bytes_progressed = 1024 * 1024; // 1MB - uint64_t bytes_received = 2 * 1024 * 1024; // 2MB - uint64_t bytes_total = 20 * 1024 * 1024; // 300MB - attempter_.status_ = UpdateStatus::CHECKING_FOR_UPDATE; - // This is set via inspecting the InstallPlan payloads when the - // OmahaResponseAction is completed - attempter_.new_payload_size_ = bytes_total; - EXPECT_EQ(0.0, attempter_.download_progress_); - NiceMock<MockServiceObserver> observer; - EXPECT_CALL(observer, - SendStatusUpdate(AllOf( - Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING), - Field(&UpdateEngineStatus::new_size_bytes, bytes_total)))); - attempter_.AddObserver(&observer); - attempter_.BytesReceived(bytes_progressed, bytes_received, bytes_total); - EXPECT_EQ(UpdateStatus::DOWNLOADING, attempter_.status_); -} - -TEST_F(UpdateAttempterTest, BroadcastCompleteDownloadTest) { - // There is a special case to ensure that at 100% downloaded, - // download_progress_ is updated and that value broadcast. This test confirms - // that. - uint64_t bytes_progressed = 0; // ignored - uint64_t bytes_received = 5 * 1024 * 1024; // ignored - uint64_t bytes_total = 5 * 1024 * 1024; // 300MB - attempter_.status_ = UpdateStatus::DOWNLOADING; - attempter_.new_payload_size_ = bytes_total; - EXPECT_EQ(0.0, attempter_.download_progress_); - NiceMock<MockServiceObserver> observer; - EXPECT_CALL(observer, - SendStatusUpdate(AllOf( - Field(&UpdateEngineStatus::progress, 1.0), - Field(&UpdateEngineStatus::status, UpdateStatus::DOWNLOADING), - Field(&UpdateEngineStatus::new_size_bytes, bytes_total)))); - attempter_.AddObserver(&observer); - attempter_.BytesReceived(bytes_progressed, bytes_received, bytes_total); - EXPECT_EQ(1.0, attempter_.download_progress_); -} - -TEST_F(UpdateAttempterTest, ActionCompletedOmahaRequestTest) { - unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr)); - fetcher->FailTransfer(500); // Sets the HTTP response code. - OmahaRequestAction action( - &fake_system_state_, nullptr, std::move(fetcher), false); - ObjectCollectorAction<OmahaResponse> collector_action; - BondActions(&action, &collector_action); - OmahaResponse response; - response.poll_interval = 234; - action.SetOutputObject(response); - EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)).Times(0); - attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess); - EXPECT_EQ(500, attempter_.http_response_code()); - EXPECT_EQ(UpdateStatus::IDLE, attempter_.status()); - EXPECT_EQ(234U, attempter_.server_dictated_poll_interval_); - ASSERT_TRUE(attempter_.error_event_.get() == nullptr); -} - -TEST_F(UpdateAttempterTest, ConstructWithUpdatedMarkerTest) { - FakePrefs fake_prefs; - string boot_id; - EXPECT_TRUE(utils::GetBootId(&boot_id)); - fake_prefs.SetString(kPrefsUpdateCompletedOnBootId, boot_id); - fake_system_state_.set_prefs(&fake_prefs); - attempter_.Init(); - EXPECT_EQ(UpdateStatus::UPDATED_NEED_REBOOT, attempter_.status()); -} - -TEST_F(UpdateAttempterTest, GetErrorCodeForActionTest) { - EXPECT_EQ(ErrorCode::kSuccess, - GetErrorCodeForAction(nullptr, ErrorCode::kSuccess)); - - FakeSystemState fake_system_state; - OmahaRequestAction omaha_request_action( - &fake_system_state, nullptr, nullptr, false); - EXPECT_EQ(ErrorCode::kOmahaRequestError, - GetErrorCodeForAction(&omaha_request_action, ErrorCode::kError)); - OmahaResponseHandlerAction omaha_response_handler_action(&fake_system_state_); - EXPECT_EQ( - ErrorCode::kOmahaResponseHandlerError, - GetErrorCodeForAction(&omaha_response_handler_action, ErrorCode::kError)); - FilesystemVerifierAction filesystem_verifier_action; - EXPECT_EQ( - ErrorCode::kFilesystemVerifierError, - GetErrorCodeForAction(&filesystem_verifier_action, ErrorCode::kError)); - PostinstallRunnerAction postinstall_runner_action( - fake_system_state.fake_boot_control(), fake_system_state.fake_hardware()); - EXPECT_EQ( - ErrorCode::kPostinstallRunnerError, - GetErrorCodeForAction(&postinstall_runner_action, ErrorCode::kError)); - MockAction action_mock; - EXPECT_CALL(action_mock, Type()).WillOnce(Return("MockAction")); - EXPECT_EQ(ErrorCode::kError, - GetErrorCodeForAction(&action_mock, ErrorCode::kError)); -} - -TEST_F(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest) { - attempter_.omaha_request_params_->set_delta_okay(true); - EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)) - .WillOnce(Return(false)); - attempter_.DisableDeltaUpdateIfNeeded(); - EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay()); - EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)) - .WillOnce( - DoAll(SetArgPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures - 1), - Return(true))); - attempter_.DisableDeltaUpdateIfNeeded(); - EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay()); - EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)) - .WillOnce( - DoAll(SetArgPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures), - Return(true))); - attempter_.DisableDeltaUpdateIfNeeded(); - EXPECT_FALSE(attempter_.omaha_request_params_->delta_okay()); - EXPECT_CALL(*prefs_, GetInt64(_, _)).Times(0); - attempter_.DisableDeltaUpdateIfNeeded(); - EXPECT_FALSE(attempter_.omaha_request_params_->delta_okay()); -} - -TEST_F(UpdateAttempterTest, MarkDeltaUpdateFailureTest) { - EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)) - .WillOnce(Return(false)) - .WillOnce(DoAll(SetArgPointee<1>(-1), Return(true))) - .WillOnce(DoAll(SetArgPointee<1>(1), Return(true))) - .WillOnce( - DoAll(SetArgPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures), - Return(true))); - EXPECT_CALL(*prefs_, SetInt64(Ne(kPrefsDeltaUpdateFailures), _)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*prefs_, SetInt64(kPrefsDeltaUpdateFailures, 1)).Times(2); - EXPECT_CALL(*prefs_, SetInt64(kPrefsDeltaUpdateFailures, 2)); - EXPECT_CALL(*prefs_, - SetInt64(kPrefsDeltaUpdateFailures, - UpdateAttempter::kMaxDeltaUpdateFailures + 1)); - for (int i = 0; i < 4; i++) - attempter_.MarkDeltaUpdateFailure(); -} - -TEST_F(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest) { - EXPECT_CALL(*processor_, EnqueueAction(_)).Times(0); - EXPECT_CALL(*processor_, StartProcessing()).Times(0); - EXPECT_CALL(*fake_system_state_.mock_payload_state(), UpdateFailed(_)) - .Times(0); - OmahaResponse response; - string url1 = "http://url1"; - response.packages.push_back({.payload_urls = {url1, "https://url"}}); - EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetCurrentUrl()) - .WillRepeatedly(Return(url1)); - fake_system_state_.mock_payload_state()->SetResponse(response); - attempter_.ScheduleErrorEventAction(); - EXPECT_EQ(url1, fake_system_state_.mock_payload_state()->GetCurrentUrl()); -} - -TEST_F(UpdateAttempterTest, ScheduleErrorEventActionTest) { - EXPECT_CALL(*processor_, - EnqueueAction(Pointee(Property( - &AbstractAction::Type, OmahaRequestAction::StaticType())))); - EXPECT_CALL(*processor_, StartProcessing()); - ErrorCode err = ErrorCode::kError; - EXPECT_CALL(*fake_system_state_.mock_payload_state(), UpdateFailed(err)); - attempter_.error_event_.reset(new OmahaEvent( - OmahaEvent::kTypeUpdateComplete, OmahaEvent::kResultError, err)); - attempter_.ScheduleErrorEventAction(); - EXPECT_EQ(UpdateStatus::REPORTING_ERROR_EVENT, attempter_.status()); -} - -namespace { -// Actions that will be built as part of an update check. -const string kUpdateActionTypes[] = { // NOLINT(runtime/string) - OmahaRequestAction::StaticType(), - OmahaResponseHandlerAction::StaticType(), - UpdateBootFlagsAction::StaticType(), - OmahaRequestAction::StaticType(), - DownloadAction::StaticType(), - OmahaRequestAction::StaticType(), - FilesystemVerifierAction::StaticType(), - PostinstallRunnerAction::StaticType(), - OmahaRequestAction::StaticType()}; - -// Actions that will be built as part of a user-initiated rollback. -const string kRollbackActionTypes[] = { - // NOLINT(runtime/string) - InstallPlanAction::StaticType(), - PostinstallRunnerAction::StaticType(), -}; - -const StagingSchedule kValidStagingSchedule = { - {4, 10}, {10, 40}, {19, 70}, {26, 100}}; - -} // namespace - -void UpdateAttempterTest::UpdateTestStart() { - attempter_.set_http_response_code(200); - - // Expect that the device policy is loaded by the UpdateAttempter at some - // point by calling RefreshDevicePolicy. - auto device_policy = std::make_unique<policy::MockDevicePolicy>(); - EXPECT_CALL(*device_policy, LoadPolicy()) - .Times(testing::AtLeast(1)) - .WillRepeatedly(Return(true)); - attempter_.policy_provider_.reset( - new policy::PolicyProvider(std::move(device_policy))); - - { - InSequence s; - for (size_t i = 0; i < arraysize(kUpdateActionTypes); ++i) { - EXPECT_CALL(*processor_, - EnqueueAction(Pointee( - Property(&AbstractAction::Type, kUpdateActionTypes[i])))); - } - EXPECT_CALL(*processor_, StartProcessing()); - } - - attempter_.Update("", "", "", "", false, false, false); - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::UpdateTestVerify, - base::Unretained(this))); -} - -void UpdateAttempterTest::UpdateTestVerify() { - EXPECT_EQ(0, attempter_.http_response_code()); - EXPECT_EQ(&attempter_, processor_->delegate()); - EXPECT_EQ(UpdateStatus::CHECKING_FOR_UPDATE, attempter_.status()); - loop_.BreakLoop(); -} - -void UpdateAttempterTest::RollbackTestStart(bool enterprise_rollback, - bool valid_slot) { - // Create a device policy so that we can change settings. - auto device_policy = std::make_unique<policy::MockDevicePolicy>(); - EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true)); - fake_system_state_.set_device_policy(device_policy.get()); - if (enterprise_rollback) { - // We return an empty owner as this is an enterprise. - EXPECT_CALL(*device_policy, GetOwner(_)) - .WillRepeatedly(DoAll(SetArgPointee<0>(string("")), Return(true))); - } else { - // We return a fake owner as this is an owned consumer device. - EXPECT_CALL(*device_policy, GetOwner(_)) - .WillRepeatedly(DoAll(SetArgPointee<0>(string("fake.mail@fake.com")), - Return(true))); - } - - attempter_.policy_provider_.reset( - new policy::PolicyProvider(std::move(device_policy))); - - if (valid_slot) { - BootControlInterface::Slot rollback_slot = 1; - LOG(INFO) << "Test Mark Bootable: " - << BootControlInterface::SlotName(rollback_slot); - fake_system_state_.fake_boot_control()->SetSlotBootable(rollback_slot, - true); - } - - bool is_rollback_allowed = false; - - // We only allow rollback on devices that are not enterprise enrolled and - // which have a valid slot to rollback to. - if (!enterprise_rollback && valid_slot) { - is_rollback_allowed = true; - } - - if (is_rollback_allowed) { - InSequence s; - for (size_t i = 0; i < arraysize(kRollbackActionTypes); ++i) { - EXPECT_CALL(*processor_, - EnqueueAction(Pointee(Property(&AbstractAction::Type, - kRollbackActionTypes[i])))); - } - EXPECT_CALL(*processor_, StartProcessing()); - - EXPECT_TRUE(attempter_.Rollback(true)); - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::RollbackTestVerify, - base::Unretained(this))); - } else { - EXPECT_FALSE(attempter_.Rollback(true)); - loop_.BreakLoop(); - } -} - -void UpdateAttempterTest::RollbackTestVerify() { - // Verifies the actions that were enqueued. - EXPECT_EQ(&attempter_, processor_->delegate()); - EXPECT_EQ(UpdateStatus::ATTEMPTING_ROLLBACK, attempter_.status()); - EXPECT_EQ(0U, attempter_.install_plan_->partitions.size()); - EXPECT_EQ(attempter_.install_plan_->powerwash_required, true); - loop_.BreakLoop(); -} - -TEST_F(UpdateAttempterTest, UpdateTest) { - UpdateTestStart(); - loop_.Run(); -} - -TEST_F(UpdateAttempterTest, RollbackTest) { - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::RollbackTestStart, - base::Unretained(this), - false, - true)); - loop_.Run(); -} - -TEST_F(UpdateAttempterTest, InvalidSlotRollbackTest) { - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::RollbackTestStart, - base::Unretained(this), - false, - false)); - loop_.Run(); -} - -TEST_F(UpdateAttempterTest, EnterpriseRollbackTest) { - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::RollbackTestStart, - base::Unretained(this), - true, - true)); - loop_.Run(); -} - -void UpdateAttempterTest::PingOmahaTestStart() { - EXPECT_CALL(*processor_, - EnqueueAction(Pointee(Property( - &AbstractAction::Type, OmahaRequestAction::StaticType())))); - EXPECT_CALL(*processor_, StartProcessing()); - attempter_.PingOmaha(); - ScheduleQuitMainLoop(); -} - -TEST_F(UpdateAttempterTest, PingOmahaTest) { - EXPECT_FALSE(attempter_.waiting_for_scheduled_check_); - EXPECT_FALSE(attempter_.schedule_updates_called()); - // Disable scheduling of subsequnet checks; we're using the DefaultPolicy in - // testing, which is more permissive than we want to handle here. - attempter_.DisableScheduleUpdates(); - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::PingOmahaTestStart, - base::Unretained(this))); - brillo::MessageLoopRunMaxIterations(&loop_, 100); - EXPECT_EQ(UpdateStatus::UPDATED_NEED_REBOOT, attempter_.status()); - EXPECT_TRUE(attempter_.schedule_updates_called()); -} - -TEST_F(UpdateAttempterTest, CreatePendingErrorEventTest) { - MockAction action; - const ErrorCode kCode = ErrorCode::kDownloadTransferError; - attempter_.CreatePendingErrorEvent(&action, kCode); - ASSERT_NE(nullptr, attempter_.error_event_.get()); - EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type); - EXPECT_EQ(OmahaEvent::kResultError, attempter_.error_event_->result); - EXPECT_EQ( - static_cast<ErrorCode>(static_cast<int>(kCode) | - static_cast<int>(ErrorCode::kTestOmahaUrlFlag)), - attempter_.error_event_->error_code); -} - -TEST_F(UpdateAttempterTest, CreatePendingErrorEventResumedTest) { - attempter_.install_plan_.reset(new InstallPlan); - attempter_.install_plan_->is_resume = true; - MockAction action; - const ErrorCode kCode = ErrorCode::kInstallDeviceOpenError; - attempter_.CreatePendingErrorEvent(&action, kCode); - ASSERT_NE(nullptr, attempter_.error_event_.get()); - EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type); - EXPECT_EQ(OmahaEvent::kResultError, attempter_.error_event_->result); - EXPECT_EQ( - static_cast<ErrorCode>(static_cast<int>(kCode) | - static_cast<int>(ErrorCode::kResumedFlag) | - static_cast<int>(ErrorCode::kTestOmahaUrlFlag)), - attempter_.error_event_->error_code); -} - -TEST_F(UpdateAttempterTest, P2PNotStartedAtStartupWhenNotEnabled) { - MockP2PManager mock_p2p_manager; - fake_system_state_.set_p2p_manager(&mock_p2p_manager); - mock_p2p_manager.fake().SetP2PEnabled(false); - EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning()).Times(0); - attempter_.UpdateEngineStarted(); -} - -TEST_F(UpdateAttempterTest, P2PNotStartedAtStartupWhenEnabledButNotSharing) { - MockP2PManager mock_p2p_manager; - fake_system_state_.set_p2p_manager(&mock_p2p_manager); - mock_p2p_manager.fake().SetP2PEnabled(true); - EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning()).Times(0); - attempter_.UpdateEngineStarted(); -} - -TEST_F(UpdateAttempterTest, P2PStartedAtStartupWhenEnabledAndSharing) { - MockP2PManager mock_p2p_manager; - fake_system_state_.set_p2p_manager(&mock_p2p_manager); - mock_p2p_manager.fake().SetP2PEnabled(true); - mock_p2p_manager.fake().SetCountSharedFilesResult(1); - EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning()); - attempter_.UpdateEngineStarted(); -} - -TEST_F(UpdateAttempterTest, P2PNotEnabled) { - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::P2PNotEnabledStart, - base::Unretained(this))); - loop_.Run(); -} - -void UpdateAttempterTest::P2PNotEnabledStart() { - // If P2P is not enabled, check that we do not attempt housekeeping - // and do not convey that p2p is to be used. - MockP2PManager mock_p2p_manager; - fake_system_state_.set_p2p_manager(&mock_p2p_manager); - mock_p2p_manager.fake().SetP2PEnabled(false); - EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()).Times(0); - attempter_.Update("", "", "", "", false, false, false); - EXPECT_FALSE(actual_using_p2p_for_downloading_); - EXPECT_FALSE(actual_using_p2p_for_sharing()); - ScheduleQuitMainLoop(); -} - -TEST_F(UpdateAttempterTest, P2PEnabledStartingFails) { - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::P2PEnabledStartingFailsStart, - base::Unretained(this))); - loop_.Run(); -} - -void UpdateAttempterTest::P2PEnabledStartingFailsStart() { - // If p2p is enabled, but starting it fails ensure we don't do - // any housekeeping and do not convey that p2p should be used. - MockP2PManager mock_p2p_manager; - fake_system_state_.set_p2p_manager(&mock_p2p_manager); - mock_p2p_manager.fake().SetP2PEnabled(true); - mock_p2p_manager.fake().SetEnsureP2PRunningResult(false); - mock_p2p_manager.fake().SetPerformHousekeepingResult(false); - EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()).Times(0); - attempter_.Update("", "", "", "", false, false, false); - EXPECT_FALSE(actual_using_p2p_for_downloading()); - EXPECT_FALSE(actual_using_p2p_for_sharing()); - ScheduleQuitMainLoop(); -} - -TEST_F(UpdateAttempterTest, P2PEnabledHousekeepingFails) { - loop_.PostTask( - FROM_HERE, - base::Bind(&UpdateAttempterTest::P2PEnabledHousekeepingFailsStart, - base::Unretained(this))); - loop_.Run(); -} - -void UpdateAttempterTest::P2PEnabledHousekeepingFailsStart() { - // If p2p is enabled, starting it works but housekeeping fails, ensure - // we do not convey p2p is to be used. - MockP2PManager mock_p2p_manager; - fake_system_state_.set_p2p_manager(&mock_p2p_manager); - mock_p2p_manager.fake().SetP2PEnabled(true); - mock_p2p_manager.fake().SetEnsureP2PRunningResult(true); - mock_p2p_manager.fake().SetPerformHousekeepingResult(false); - EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()); - attempter_.Update("", "", "", "", false, false, false); - EXPECT_FALSE(actual_using_p2p_for_downloading()); - EXPECT_FALSE(actual_using_p2p_for_sharing()); - ScheduleQuitMainLoop(); -} - -TEST_F(UpdateAttempterTest, P2PEnabled) { - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::P2PEnabledStart, - base::Unretained(this))); - loop_.Run(); -} - -void UpdateAttempterTest::P2PEnabledStart() { - MockP2PManager mock_p2p_manager; - fake_system_state_.set_p2p_manager(&mock_p2p_manager); - // If P2P is enabled and starting it works, check that we performed - // housekeeping and that we convey p2p should be used. - mock_p2p_manager.fake().SetP2PEnabled(true); - mock_p2p_manager.fake().SetEnsureP2PRunningResult(true); - mock_p2p_manager.fake().SetPerformHousekeepingResult(true); - EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()); - attempter_.Update("", "", "", "", false, false, false); - EXPECT_TRUE(actual_using_p2p_for_downloading()); - EXPECT_TRUE(actual_using_p2p_for_sharing()); - ScheduleQuitMainLoop(); -} - -TEST_F(UpdateAttempterTest, P2PEnabledInteractive) { - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::P2PEnabledInteractiveStart, - base::Unretained(this))); - loop_.Run(); -} - -void UpdateAttempterTest::P2PEnabledInteractiveStart() { - MockP2PManager mock_p2p_manager; - fake_system_state_.set_p2p_manager(&mock_p2p_manager); - // For an interactive check, if P2P is enabled and starting it - // works, check that we performed housekeeping and that we convey - // p2p should be used for sharing but NOT for downloading. - mock_p2p_manager.fake().SetP2PEnabled(true); - mock_p2p_manager.fake().SetEnsureP2PRunningResult(true); - mock_p2p_manager.fake().SetPerformHousekeepingResult(true); - EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()); - attempter_.Update("", - "", - "", - "", - false, - false, - /*interactive=*/true); - EXPECT_FALSE(actual_using_p2p_for_downloading()); - EXPECT_TRUE(actual_using_p2p_for_sharing()); - ScheduleQuitMainLoop(); -} - -TEST_F(UpdateAttempterTest, ReadScatterFactorFromPolicy) { - loop_.PostTask( - FROM_HERE, - base::Bind(&UpdateAttempterTest::ReadScatterFactorFromPolicyTestStart, - base::Unretained(this))); - loop_.Run(); -} - -// Tests that the scatter_factor_in_seconds value is properly fetched -// from the device policy. -void UpdateAttempterTest::ReadScatterFactorFromPolicyTestStart() { - int64_t scatter_factor_in_seconds = 36000; - - auto device_policy = std::make_unique<policy::MockDevicePolicy>(); - EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true)); - fake_system_state_.set_device_policy(device_policy.get()); - - EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_)) - .WillRepeatedly( - DoAll(SetArgPointee<0>(scatter_factor_in_seconds), Return(true))); - - attempter_.policy_provider_.reset( - new policy::PolicyProvider(std::move(device_policy))); - - attempter_.Update("", "", "", "", false, false, false); - EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds()); - - ScheduleQuitMainLoop(); -} - -TEST_F(UpdateAttempterTest, DecrementUpdateCheckCountTest) { - loop_.PostTask( - FROM_HERE, - base::Bind(&UpdateAttempterTest::DecrementUpdateCheckCountTestStart, - base::Unretained(this))); - loop_.Run(); -} - -void UpdateAttempterTest::DecrementUpdateCheckCountTestStart() { - // Tests that the scatter_factor_in_seconds value is properly fetched - // from the device policy and is decremented if value > 0. - int64_t initial_value = 5; - FakePrefs fake_prefs; - attempter_.prefs_ = &fake_prefs; - - fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch()); - - EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value)); - - int64_t scatter_factor_in_seconds = 10; - - auto device_policy = std::make_unique<policy::MockDevicePolicy>(); - EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true)); - fake_system_state_.set_device_policy(device_policy.get()); - - EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_)) - .WillRepeatedly( - DoAll(SetArgPointee<0>(scatter_factor_in_seconds), Return(true))); - - attempter_.policy_provider_.reset( - new policy::PolicyProvider(std::move(device_policy))); - - attempter_.Update("", "", "", "", false, false, false); - EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds()); - - // Make sure the file still exists. - EXPECT_TRUE(fake_prefs.Exists(kPrefsUpdateCheckCount)); - - int64_t new_value; - EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &new_value)); - EXPECT_EQ(initial_value - 1, new_value); - - EXPECT_TRUE( - attempter_.omaha_request_params_->update_check_count_wait_enabled()); - - // However, if the count is already 0, it's not decremented. Test that. - initial_value = 0; - EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value)); - attempter_.Update("", "", "", "", false, false, false); - EXPECT_TRUE(fake_prefs.Exists(kPrefsUpdateCheckCount)); - EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &new_value)); - EXPECT_EQ(initial_value, new_value); - - ScheduleQuitMainLoop(); -} - -TEST_F(UpdateAttempterTest, NoScatteringDoneDuringManualUpdateTestStart) { - loop_.PostTask( - FROM_HERE, - base::Bind( - &UpdateAttempterTest::NoScatteringDoneDuringManualUpdateTestStart, - base::Unretained(this))); - loop_.Run(); -} - -void UpdateAttempterTest::NoScatteringDoneDuringManualUpdateTestStart() { - // Tests that no scattering logic is enabled if the update check - // is manually done (as opposed to a scheduled update check) - int64_t initial_value = 8; - FakePrefs fake_prefs; - attempter_.prefs_ = &fake_prefs; - - fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch()); - fake_system_state_.set_prefs(&fake_prefs); - - EXPECT_TRUE( - fake_prefs.SetInt64(kPrefsWallClockScatteringWaitPeriod, initial_value)); - EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value)); - - // make sure scatter_factor is non-zero as scattering is disabled - // otherwise. - int64_t scatter_factor_in_seconds = 50; - - auto device_policy = std::make_unique<policy::MockDevicePolicy>(); - EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true)); - fake_system_state_.set_device_policy(device_policy.get()); - - EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_)) - .WillRepeatedly( - DoAll(SetArgPointee<0>(scatter_factor_in_seconds), Return(true))); - - attempter_.policy_provider_.reset( - new policy::PolicyProvider(std::move(device_policy))); - - // Trigger an interactive check so we can test that scattering is disabled. - attempter_.Update("", - "", - "", - "", - false, - false, - /*interactive=*/true); - EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds()); - - // Make sure scattering is disabled for manual (i.e. user initiated) update - // checks and all artifacts are removed. - EXPECT_FALSE( - attempter_.omaha_request_params_->wall_clock_based_wait_enabled()); - EXPECT_FALSE(fake_prefs.Exists(kPrefsWallClockScatteringWaitPeriod)); - EXPECT_EQ(0, attempter_.omaha_request_params_->waiting_period().InSeconds()); - EXPECT_FALSE( - attempter_.omaha_request_params_->update_check_count_wait_enabled()); - EXPECT_FALSE(fake_prefs.Exists(kPrefsUpdateCheckCount)); - - ScheduleQuitMainLoop(); -} - -void UpdateAttempterTest::SetUpStagingTest(const StagingSchedule& schedule, - FakePrefs* prefs) { - attempter_.prefs_ = prefs; - fake_system_state_.set_prefs(prefs); - - int64_t initial_value = 8; - EXPECT_TRUE( - prefs->SetInt64(kPrefsWallClockScatteringWaitPeriod, initial_value)); - EXPECT_TRUE(prefs->SetInt64(kPrefsUpdateCheckCount, initial_value)); - attempter_.scatter_factor_ = TimeDelta::FromSeconds(20); - - auto device_policy = std::make_unique<policy::MockDevicePolicy>(); - EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true)); - fake_system_state_.set_device_policy(device_policy.get()); - EXPECT_CALL(*device_policy, GetDeviceUpdateStagingSchedule(_)) - .WillRepeatedly(DoAll(SetArgPointee<0>(schedule), Return(true))); - - attempter_.policy_provider_.reset( - new policy::PolicyProvider(std::move(device_policy))); -} - -TEST_F(UpdateAttempterTest, StagingSetsPrefsAndTurnsOffScattering) { - loop_.PostTask( - FROM_HERE, - base::Bind( - &UpdateAttempterTest::StagingSetsPrefsAndTurnsOffScatteringStart, - base::Unretained(this))); - loop_.Run(); -} - -void UpdateAttempterTest::StagingSetsPrefsAndTurnsOffScatteringStart() { - // Tests that staging sets its prefs properly and turns off scattering. - fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch()); - FakePrefs fake_prefs; - SetUpStagingTest(kValidStagingSchedule, &fake_prefs); - - attempter_.Update("", "", "", "", false, false, false); - // Check that prefs have the correct values. - int64_t update_count; - EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &update_count)); - int64_t waiting_time_days; - EXPECT_TRUE(fake_prefs.GetInt64(kPrefsWallClockStagingWaitPeriod, - &waiting_time_days)); - EXPECT_GT(waiting_time_days, 0); - // Update count should have been decremented. - EXPECT_EQ(7, update_count); - // Check that Omaha parameters were updated correctly. - EXPECT_TRUE( - attempter_.omaha_request_params_->update_check_count_wait_enabled()); - EXPECT_TRUE( - attempter_.omaha_request_params_->wall_clock_based_wait_enabled()); - EXPECT_EQ(waiting_time_days, - attempter_.omaha_request_params_->waiting_period().InDays()); - // Check class variables. - EXPECT_EQ(waiting_time_days, attempter_.staging_wait_time_.InDays()); - EXPECT_EQ(kValidStagingSchedule, attempter_.staging_schedule_); - // Check that scattering is turned off - EXPECT_EQ(0, attempter_.scatter_factor_.InSeconds()); - EXPECT_FALSE(fake_prefs.Exists(kPrefsWallClockScatteringWaitPeriod)); - - ScheduleQuitMainLoop(); -} - -void UpdateAttempterTest::CheckStagingOff() { - // Check that all prefs were removed. - EXPECT_FALSE(attempter_.prefs_->Exists(kPrefsUpdateCheckCount)); - EXPECT_FALSE(attempter_.prefs_->Exists(kPrefsWallClockScatteringWaitPeriod)); - EXPECT_FALSE(attempter_.prefs_->Exists(kPrefsWallClockStagingWaitPeriod)); - // Check that the Omaha parameters have the correct value. - EXPECT_EQ(0, attempter_.omaha_request_params_->waiting_period().InDays()); - EXPECT_EQ(attempter_.omaha_request_params_->waiting_period(), - attempter_.staging_wait_time_); - EXPECT_FALSE( - attempter_.omaha_request_params_->update_check_count_wait_enabled()); - EXPECT_FALSE( - attempter_.omaha_request_params_->wall_clock_based_wait_enabled()); - // Check that scattering is turned off too. - EXPECT_EQ(0, attempter_.scatter_factor_.InSeconds()); -} - -TEST_F(UpdateAttempterTest, StagingOffIfInteractive) { - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::StagingOffIfInteractiveStart, - base::Unretained(this))); - loop_.Run(); -} - -void UpdateAttempterTest::StagingOffIfInteractiveStart() { - // Tests that staging is turned off when an interactive update is requested. - fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch()); - FakePrefs fake_prefs; - SetUpStagingTest(kValidStagingSchedule, &fake_prefs); - - attempter_.Update("", "", "", "", false, false, /* interactive = */ true); - CheckStagingOff(); - - ScheduleQuitMainLoop(); -} - -TEST_F(UpdateAttempterTest, StagingOffIfOobe) { - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::StagingOffIfOobeStart, - base::Unretained(this))); - loop_.Run(); -} - -void UpdateAttempterTest::StagingOffIfOobeStart() { - // Tests that staging is turned off if OOBE hasn't been completed. - fake_system_state_.fake_hardware()->SetIsOOBEEnabled(true); - fake_system_state_.fake_hardware()->UnsetIsOOBEComplete(); - FakePrefs fake_prefs; - SetUpStagingTest(kValidStagingSchedule, &fake_prefs); - - attempter_.Update("", "", "", "", false, false, /* interactive = */ true); - CheckStagingOff(); - - ScheduleQuitMainLoop(); -} - -// Checks that we only report daily metrics at most every 24 hours. -TEST_F(UpdateAttempterTest, ReportDailyMetrics) { - FakeClock fake_clock; - FakePrefs fake_prefs; - - fake_system_state_.set_clock(&fake_clock); - fake_system_state_.set_prefs(&fake_prefs); - - Time epoch = Time::FromInternalValue(0); - fake_clock.SetWallclockTime(epoch); - - // If there is no kPrefsDailyMetricsLastReportedAt state variable, - // we should report. - EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics()); - // We should not report again if no time has passed. - EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); - - // We should not report if only 10 hours has passed. - fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(10)); - EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); - - // We should not report if only 24 hours - 1 sec has passed. - fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(24) - - TimeDelta::FromSeconds(1)); - EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); - - // We should report if 24 hours has passed. - fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(24)); - EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics()); - - // But then we should not report again.. - EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); - - // .. until another 24 hours has passed - fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(47)); - EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); - fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(48)); - EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics()); - EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); - - // .. and another 24 hours - fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(71)); - EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); - fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(72)); - EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics()); - EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); - - // If the span between time of reporting and present time is - // negative, we report. This is in order to reset the timestamp and - // avoid an edge condition whereby a distant point in the future is - // in the state variable resulting in us never ever reporting again. - fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(71)); - EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics()); - EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); - - // In this case we should not update until the clock reads 71 + 24 = 95. - // Check that. - fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(94)); - EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); - fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(95)); - EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics()); - EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics()); -} - -TEST_F(UpdateAttempterTest, BootTimeInUpdateMarkerFile) { - FakeClock fake_clock; - fake_clock.SetBootTime(Time::FromTimeT(42)); - fake_system_state_.set_clock(&fake_clock); - FakePrefs fake_prefs; - fake_system_state_.set_prefs(&fake_prefs); - attempter_.Init(); - - Time boot_time; - EXPECT_FALSE(attempter_.GetBootTimeAtUpdate(&boot_time)); - - attempter_.WriteUpdateCompletedMarker(); - - EXPECT_TRUE(attempter_.GetBootTimeAtUpdate(&boot_time)); - EXPECT_EQ(boot_time.ToTimeT(), 42); -} - -TEST_F(UpdateAttempterTest, AnyUpdateSourceAllowedUnofficial) { - fake_system_state_.fake_hardware()->SetIsOfficialBuild(false); - EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed()); -} - -TEST_F(UpdateAttempterTest, AnyUpdateSourceAllowedOfficialDevmode) { - fake_system_state_.fake_hardware()->SetIsOfficialBuild(true); - fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(true); - EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed()); -} - -TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedOfficialNormal) { - fake_system_state_.fake_hardware()->SetIsOfficialBuild(true); - fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false); - EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed()); -} - -TEST_F(UpdateAttempterTest, CheckForUpdateAUDlcTest) { - fake_system_state_.fake_hardware()->SetIsOfficialBuild(true); - fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false); - - const string dlc_module_id = "a_dlc_module_id"; - vector<string> dlc_module_ids = {dlc_module_id}; - ON_CALL(mock_dlcservice_, GetInstalled(testing::_)) - .WillByDefault(DoAll(testing::SetArgPointee<0>(dlc_module_ids), - testing::Return(true))); - - attempter_.CheckForUpdate("", "autest", UpdateAttemptFlags::kNone); - EXPECT_EQ(attempter_.dlc_module_ids_.size(), 1); - EXPECT_EQ(attempter_.dlc_module_ids_[0], dlc_module_id); -} - -TEST_F(UpdateAttempterTest, CheckForUpdateAUTest) { - fake_system_state_.fake_hardware()->SetIsOfficialBuild(true); - fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false); - attempter_.CheckForUpdate("", "autest", UpdateAttemptFlags::kNone); - EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url()); -} - -TEST_F(UpdateAttempterTest, CheckForUpdateScheduledAUTest) { - fake_system_state_.fake_hardware()->SetIsOfficialBuild(true); - fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false); - attempter_.CheckForUpdate("", "autest-scheduled", UpdateAttemptFlags::kNone); - EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url()); -} - -TEST_F(UpdateAttempterTest, CheckForInstallTest) { - fake_system_state_.fake_hardware()->SetIsOfficialBuild(true); - fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false); - attempter_.CheckForInstall({}, "autest"); - EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url()); - - attempter_.CheckForInstall({}, "autest-scheduled"); - EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url()); - - attempter_.CheckForInstall({}, "http://omaha.phishing"); - EXPECT_EQ("", attempter_.forced_omaha_url()); -} - -TEST_F(UpdateAttempterTest, InstallSetsStatusIdle) { - attempter_.CheckForInstall({}, "http://foo.bar"); - attempter_.status_ = UpdateStatus::DOWNLOADING; - EXPECT_TRUE(attempter_.is_install_); - attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); - UpdateEngineStatus status; - attempter_.GetStatus(&status); - // Should set status to idle after an install operation. - EXPECT_EQ(UpdateStatus::IDLE, status.status); -} - -TEST_F(UpdateAttempterTest, RollbackAfterInstall) { - attempter_.is_install_ = true; - attempter_.Rollback(false); - EXPECT_FALSE(attempter_.is_install_); -} - -TEST_F(UpdateAttempterTest, UpdateAfterInstall) { - attempter_.is_install_ = true; - attempter_.CheckForUpdate("", "", UpdateAttemptFlags::kNone); - EXPECT_FALSE(attempter_.is_install_); -} - -TEST_F(UpdateAttempterTest, TargetVersionPrefixSetAndReset) { - attempter_.CalculateUpdateParams("", "", "", "1234", false, false, false); - EXPECT_EQ("1234", - fake_system_state_.request_params()->target_version_prefix()); - - attempter_.CalculateUpdateParams("", "", "", "", false, false, false); - EXPECT_TRUE( - fake_system_state_.request_params()->target_version_prefix().empty()); -} - -TEST_F(UpdateAttempterTest, RollbackAllowedSetAndReset) { - attempter_.CalculateUpdateParams("", - "", - "", - "1234", - /*rollback_allowed=*/true, - false, - false); - EXPECT_TRUE(fake_system_state_.request_params()->rollback_allowed()); - - attempter_.CalculateUpdateParams("", - "", - "", - "1234", - /*rollback_allowed=*/false, - false, - false); - EXPECT_FALSE(fake_system_state_.request_params()->rollback_allowed()); -} - -TEST_F(UpdateAttempterTest, UpdateDeferredByPolicyTest) { - // Construct an OmahaResponseHandlerAction that has processed an InstallPlan, - // but the update is being deferred by the Policy. - OmahaResponseHandlerAction response_action(&fake_system_state_); - response_action.install_plan_.version = "a.b.c.d"; - response_action.install_plan_.system_version = "b.c.d.e"; - response_action.install_plan_.payloads.push_back( - {.size = 1234ULL, .type = InstallPayloadType::kFull}); - // Inform the UpdateAttempter that the OmahaResponseHandlerAction has - // completed, with the deferred-update error code. - attempter_.ActionCompleted( - nullptr, &response_action, ErrorCode::kOmahaUpdateDeferredPerPolicy); - { - UpdateEngineStatus status; - attempter_.GetStatus(&status); - EXPECT_EQ(UpdateStatus::UPDATE_AVAILABLE, status.status); - EXPECT_TRUE(attempter_.install_plan_); - EXPECT_EQ(attempter_.install_plan_->version, status.new_version); - EXPECT_EQ(attempter_.install_plan_->system_version, - status.new_system_version); - EXPECT_EQ(attempter_.install_plan_->payloads[0].size, - status.new_size_bytes); - } - // An "error" event should have been created to tell Omaha that the update is - // being deferred. - EXPECT_TRUE(nullptr != attempter_.error_event_); - EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type); - EXPECT_EQ(OmahaEvent::kResultUpdateDeferred, attempter_.error_event_->result); - ErrorCode expected_code = static_cast<ErrorCode>( - static_cast<int>(ErrorCode::kOmahaUpdateDeferredPerPolicy) | - static_cast<int>(ErrorCode::kTestOmahaUrlFlag)); - EXPECT_EQ(expected_code, attempter_.error_event_->error_code); - // End the processing - attempter_.ProcessingDone(nullptr, ErrorCode::kOmahaUpdateDeferredPerPolicy); - // Validate the state of the attempter. - { - UpdateEngineStatus status; - attempter_.GetStatus(&status); - EXPECT_EQ(UpdateStatus::REPORTING_ERROR_EVENT, status.status); - EXPECT_EQ(response_action.install_plan_.version, status.new_version); - EXPECT_EQ(response_action.install_plan_.system_version, - status.new_system_version); - EXPECT_EQ(response_action.install_plan_.payloads[0].size, - status.new_size_bytes); - } -} - -TEST_F(UpdateAttempterTest, UpdateIsNotRunningWhenUpdateAvailable) { - EXPECT_FALSE(attempter_.IsUpdateRunningOrScheduled()); - // Verify in-progress update with UPDATE_AVAILABLE is running - attempter_.status_ = UpdateStatus::UPDATE_AVAILABLE; - EXPECT_TRUE(attempter_.IsUpdateRunningOrScheduled()); -} - -TEST_F(UpdateAttempterTest, UpdateAttemptFlagsCachedAtUpdateStart) { - attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kFlagRestrictDownload); - - UpdateCheckParams params = {.updates_enabled = true}; - attempter_.OnUpdateScheduled(EvalStatus::kSucceeded, params); - - EXPECT_EQ(UpdateAttemptFlags::kFlagRestrictDownload, - attempter_.GetCurrentUpdateAttemptFlags()); -} - -TEST_F(UpdateAttempterTest, RollbackNotAllowed) { - UpdateCheckParams params = {.updates_enabled = true, - .rollback_allowed = false}; - attempter_.OnUpdateScheduled(EvalStatus::kSucceeded, params); - EXPECT_FALSE(fake_system_state_.request_params()->rollback_allowed()); -} - -TEST_F(UpdateAttempterTest, RollbackAllowed) { - UpdateCheckParams params = {.updates_enabled = true, - .rollback_allowed = true}; - attempter_.OnUpdateScheduled(EvalStatus::kSucceeded, params); - EXPECT_TRUE(fake_system_state_.request_params()->rollback_allowed()); -} - -TEST_F(UpdateAttempterTest, InteractiveUpdateUsesPassedRestrictions) { - attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kFlagRestrictDownload); - - attempter_.CheckForUpdate("", "", UpdateAttemptFlags::kNone); - EXPECT_EQ(UpdateAttemptFlags::kNone, - attempter_.GetCurrentUpdateAttemptFlags()); -} - -TEST_F(UpdateAttempterTest, NonInteractiveUpdateUsesSetRestrictions) { - attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kNone); - - // This tests that when CheckForUpdate() is called with the non-interactive - // flag set, that it doesn't change the current UpdateAttemptFlags. - attempter_.CheckForUpdate("", - "", - UpdateAttemptFlags::kFlagNonInteractive | - UpdateAttemptFlags::kFlagRestrictDownload); - EXPECT_EQ(UpdateAttemptFlags::kNone, - attempter_.GetCurrentUpdateAttemptFlags()); -} - -void UpdateAttempterTest::ResetRollbackHappenedStart(bool is_consumer, - bool is_policy_loaded, - bool expected_reset) { - EXPECT_CALL(*fake_system_state_.mock_payload_state(), GetRollbackHappened()) - .WillRepeatedly(Return(true)); - auto mock_policy_provider = - std::make_unique<NiceMock<policy::MockPolicyProvider>>(); - EXPECT_CALL(*mock_policy_provider, IsConsumerDevice()) - .WillRepeatedly(Return(is_consumer)); - EXPECT_CALL(*mock_policy_provider, device_policy_is_loaded()) - .WillRepeatedly(Return(is_policy_loaded)); - const policy::MockDevicePolicy device_policy; - EXPECT_CALL(*mock_policy_provider, GetDevicePolicy()) - .WillRepeatedly(ReturnRef(device_policy)); - EXPECT_CALL(*fake_system_state_.mock_payload_state(), - SetRollbackHappened(false)) - .Times(expected_reset ? 1 : 0); - attempter_.policy_provider_ = std::move(mock_policy_provider); - attempter_.Update("", "", "", "", false, false, false); - ScheduleQuitMainLoop(); -} - -TEST_F(UpdateAttempterTest, ResetRollbackHappenedOobe) { - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::ResetRollbackHappenedStart, - base::Unretained(this), - /*is_consumer=*/false, - /*is_policy_loaded=*/false, - /*expected_reset=*/false)); - loop_.Run(); -} - -TEST_F(UpdateAttempterTest, ResetRollbackHappenedConsumer) { - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::ResetRollbackHappenedStart, - base::Unretained(this), - /*is_consumer=*/true, - /*is_policy_loaded=*/false, - /*expected_reset=*/true)); - loop_.Run(); -} - -TEST_F(UpdateAttempterTest, ResetRollbackHappenedEnterprise) { - loop_.PostTask(FROM_HERE, - base::Bind(&UpdateAttempterTest::ResetRollbackHappenedStart, - base::Unretained(this), - /*is_consumer=*/false, - /*is_policy_loaded=*/true, - /*expected_reset=*/true)); - loop_.Run(); -} - -TEST_F(UpdateAttempterTest, SetRollbackHappenedRollback) { - attempter_.install_plan_.reset(new InstallPlan); - attempter_.install_plan_->is_rollback = true; - - EXPECT_CALL(*fake_system_state_.mock_payload_state(), - SetRollbackHappened(true)) - .Times(1); - attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); -} - -TEST_F(UpdateAttempterTest, SetRollbackHappenedNotRollback) { - attempter_.install_plan_.reset(new InstallPlan); - attempter_.install_plan_->is_rollback = false; - - EXPECT_CALL(*fake_system_state_.mock_payload_state(), - SetRollbackHappened(true)) - .Times(0); - attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); -} - -TEST_F(UpdateAttempterTest, RollbackMetricsRollbackSuccess) { - attempter_.install_plan_.reset(new InstallPlan); - attempter_.install_plan_->is_rollback = true; - attempter_.install_plan_->version = kRollbackVersion; - - EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(), - ReportEnterpriseRollbackMetrics(true, kRollbackVersion)) - .Times(1); - attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); -} - -TEST_F(UpdateAttempterTest, RollbackMetricsNotRollbackSuccess) { - attempter_.install_plan_.reset(new InstallPlan); - attempter_.install_plan_->is_rollback = false; - attempter_.install_plan_->version = kRollbackVersion; - - EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(), - ReportEnterpriseRollbackMetrics(_, _)) - .Times(0); - attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); -} - -TEST_F(UpdateAttempterTest, RollbackMetricsRollbackFailure) { - attempter_.install_plan_.reset(new InstallPlan); - attempter_.install_plan_->is_rollback = true; - attempter_.install_plan_->version = kRollbackVersion; - - EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(), - ReportEnterpriseRollbackMetrics(false, kRollbackVersion)) - .Times(1); - MockAction action; - attempter_.CreatePendingErrorEvent(&action, ErrorCode::kRollbackNotPossible); - attempter_.ProcessingDone(nullptr, ErrorCode::kRollbackNotPossible); -} - -TEST_F(UpdateAttempterTest, RollbackMetricsNotRollbackFailure) { - attempter_.install_plan_.reset(new InstallPlan); - attempter_.install_plan_->is_rollback = false; - attempter_.install_plan_->version = kRollbackVersion; - - EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(), - ReportEnterpriseRollbackMetrics(_, _)) - .Times(0); - MockAction action; - attempter_.CreatePendingErrorEvent(&action, ErrorCode::kRollbackNotPossible); - attempter_.ProcessingDone(nullptr, ErrorCode::kRollbackNotPossible); -} - -TEST_F(UpdateAttempterTest, TimeToUpdateAppliedMetricFailure) { - EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(), - ReportEnterpriseUpdateSeenToDownloadDays(_, _)) - .Times(0); - attempter_.ProcessingDone(nullptr, ErrorCode::kOmahaUpdateDeferredPerPolicy); -} - -TEST_F(UpdateAttempterTest, TimeToUpdateAppliedOnNonEnterprise) { - auto device_policy = std::make_unique<policy::MockDevicePolicy>(); - fake_system_state_.set_device_policy(device_policy.get()); - // Make device policy return that this is not enterprise enrolled - EXPECT_CALL(*device_policy, IsEnterpriseEnrolled()).WillOnce(Return(false)); - - // Ensure that the metric is not recorded. - EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(), - ReportEnterpriseUpdateSeenToDownloadDays(_, _)) - .Times(0); - attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); -} - -TEST_F(UpdateAttempterTest, - TimeToUpdateAppliedWithTimeRestrictionMetricSuccess) { - constexpr int kDaysToUpdate = 15; - auto device_policy = std::make_unique<policy::MockDevicePolicy>(); - fake_system_state_.set_device_policy(device_policy.get()); - // Make device policy return that this is enterprise enrolled - EXPECT_CALL(*device_policy, IsEnterpriseEnrolled()).WillOnce(Return(true)); - // Pretend that there's a time restriction policy in place - EXPECT_CALL(*device_policy, GetDisallowedTimeIntervals(_)) - .WillOnce(Return(true)); - - FakePrefs fake_prefs; - Time update_first_seen_at = Time::Now(); - fake_prefs.SetInt64(kPrefsUpdateFirstSeenAt, - update_first_seen_at.ToInternalValue()); - - FakeClock fake_clock; - Time update_finished_at = - update_first_seen_at + TimeDelta::FromDays(kDaysToUpdate); - fake_clock.SetWallclockTime(update_finished_at); - - fake_system_state_.set_clock(&fake_clock); - fake_system_state_.set_prefs(&fake_prefs); - - EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(), - ReportEnterpriseUpdateSeenToDownloadDays(true, kDaysToUpdate)) - .Times(1); - attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); -} - -TEST_F(UpdateAttempterTest, - TimeToUpdateAppliedWithoutTimeRestrictionMetricSuccess) { - constexpr int kDaysToUpdate = 15; - auto device_policy = std::make_unique<policy::MockDevicePolicy>(); - fake_system_state_.set_device_policy(device_policy.get()); - // Make device policy return that this is enterprise enrolled - EXPECT_CALL(*device_policy, IsEnterpriseEnrolled()).WillOnce(Return(true)); - // Pretend that there's no time restriction policy in place - EXPECT_CALL(*device_policy, GetDisallowedTimeIntervals(_)) - .WillOnce(Return(false)); - - FakePrefs fake_prefs; - Time update_first_seen_at = Time::Now(); - fake_prefs.SetInt64(kPrefsUpdateFirstSeenAt, - update_first_seen_at.ToInternalValue()); - - FakeClock fake_clock; - Time update_finished_at = - update_first_seen_at + TimeDelta::FromDays(kDaysToUpdate); - fake_clock.SetWallclockTime(update_finished_at); - - fake_system_state_.set_clock(&fake_clock); - fake_system_state_.set_prefs(&fake_prefs); - - EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(), - ReportEnterpriseUpdateSeenToDownloadDays(false, kDaysToUpdate)) - .Times(1); - attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess); -} - -} // namespace chromeos_update_engine diff --git a/update_boot_flags_action_unittest.cc b/update_boot_flags_action_unittest.cc index 1b2bfa5a..26cbe909 100644 --- a/update_boot_flags_action_unittest.cc +++ b/update_boot_flags_action_unittest.cc @@ -22,18 +22,17 @@ #include <base/bind.h> #include <gtest/gtest.h> -#include "update_engine/fake_system_state.h" +#include "update_engine/common/fake_boot_control.h" namespace chromeos_update_engine { class UpdateBootFlagsActionTest : public ::testing::Test { - public: - FakeSystemState fake_system_state_; + protected: + FakeBootControl boot_control_; }; TEST_F(UpdateBootFlagsActionTest, SimpleTest) { - auto boot_control = fake_system_state_.fake_boot_control(); - auto action = std::make_unique<UpdateBootFlagsAction>(boot_control); + auto action = std::make_unique<UpdateBootFlagsAction>(&boot_control_); ActionProcessor processor; processor.EnqueueAction(std::move(action)); @@ -49,9 +48,8 @@ TEST_F(UpdateBootFlagsActionTest, DoubleActionTest) { UpdateBootFlagsAction::updated_boot_flags_ = false; UpdateBootFlagsAction::is_running_ = false; - auto boot_control = fake_system_state_.fake_boot_control(); - auto action1 = std::make_unique<UpdateBootFlagsAction>(boot_control); - auto action2 = std::make_unique<UpdateBootFlagsAction>(boot_control); + auto action1 = std::make_unique<UpdateBootFlagsAction>(&boot_control_); + auto action2 = std::make_unique<UpdateBootFlagsAction>(&boot_control_); ActionProcessor processor1, processor2; processor1.EnqueueAction(std::move(action1)); processor2.EnqueueAction(std::move(action2)); diff --git a/update_engine-client.gyp b/update_engine-client.gyp deleted file mode 100644 index 588fc63b..00000000 --- a/update_engine-client.gyp +++ /dev/null @@ -1,41 +0,0 @@ -# -# Copyright (C) 2015 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. -# -{ - 'targets': [ - # update_engine client library generated headers. Used by other daemons and - # by the update_engine_client console program to interact with - # update_engine. - { - 'target_name': 'libupdate_engine-client-headers', - 'type': 'none', - 'actions': [ - { - 'action_name': 'update_engine_client-dbus-proxies', - 'variables': { - 'dbus_service_config': 'dbus_bindings/dbus-service-config.json', - 'proxy_output_file': 'include/update_engine/dbus-proxies.h', - 'mock_output_file': 'include/update_engine/dbus-proxy-mocks.h', - 'proxy_path_in_mocks': 'update_engine/dbus-proxies.h', - }, - 'sources': [ - 'dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml', - ], - 'includes': ['../../../platform2/common-mk/generate-dbus-proxies.gypi'], - }, - ], - }, - ], -} 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.conf.chromeos b/update_engine.conf.chromeos new file mode 100644 index 00000000..af213ad9 --- /dev/null +++ b/update_engine.conf.chromeos @@ -0,0 +1,2 @@ +PAYLOAD_MAJOR_VERSION=2 +PAYLOAD_MINOR_VERSION=6 diff --git a/update_engine.gyp b/update_engine.gyp deleted file mode 100644 index c2c0c62b..00000000 --- a/update_engine.gyp +++ /dev/null @@ -1,655 +0,0 @@ -# -# Copyright (C) 2015 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. -# - -# TODO: Rename these files to pass this check. -# gyplint: disable=GypLintSourceFileNames -{ - 'variables': { - 'USE_chrome_network_proxy': '1', - 'USE_chrome_kiosk_app': '1', - }, - 'target_defaults': { - 'variables': { - 'deps': [ - 'libbrillo-<(libbase_ver)', - 'libchrome-<(libbase_ver)', - # system_api depends on protobuf (or protobuf-lite). It must appear - # before protobuf here or the linker flags won't be in the right - # order. - 'system_api', - 'protobuf-lite', - ], - # The -DUSE_* flags are passed from platform2.py. We use sane defaults - # here when these USE flags are not defined. You can set the default value - # for the USE flag in the ebuild. - 'USE_hwid_override%': '0', - }, - 'cflags': [ - '-g', - '-ffunction-sections', - '-Wall', - '-Wextra', - '-Werror', - '-Wno-unused-parameter', - ], - 'cflags_cc': [ - '-fno-strict-aliasing', - '-Wnon-virtual-dtor', - ], - 'ldflags': [ - '-Wl,--gc-sections', - ], - 'defines': [ - '__CHROMEOS__', - '_FILE_OFFSET_BITS=64', - '_POSIX_C_SOURCE=199309L', - 'USE_BINDER=<(USE_binder)', - 'USE_DBUS=<(USE_dbus)', - 'USE_FEC=0', - 'USE_HWID_OVERRIDE=<(USE_hwid_override)', - 'USE_CHROME_KIOSK_APP=<(USE_chrome_kiosk_app)', - 'USE_CHROME_NETWORK_PROXY=<(USE_chrome_network_proxy)', - 'USE_MTD=<(USE_mtd)', - 'USE_OMAHA=1', - 'USE_SHILL=1', - ], - 'include_dirs': [ - # We need this include dir because we include all the local code as - # "update_engine/...". - '<(platform2_root)/../aosp/system', - '<(platform2_root)/../aosp/system/update_engine/client_library/include', - ], - }, - 'targets': [ - # Protobufs. - { - 'target_name': 'update_metadata-protos', - 'type': 'static_library', - 'variables': { - 'proto_in_dir': '.', - 'proto_out_dir': 'include/update_engine', - }, - 'sources': [ - 'update_metadata.proto', - ], - 'includes': ['../../../platform2/common-mk/protoc.gypi'], - }, - # Chrome D-Bus bindings. - { - 'target_name': 'update_engine-dbus-adaptor', - 'type': 'none', - 'variables': { - 'dbus_adaptors_out_dir': 'include/dbus_bindings', - 'dbus_xml_extension': 'dbus-xml', - }, - 'sources': [ - 'dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml', - ], - 'includes': ['../../../platform2/common-mk/generate-dbus-adaptors.gypi'], - }, - { - 'target_name': 'update_engine-dbus-kiosk-app-client', - 'type': 'none', - 'actions': [{ - 'action_name': 'update_engine-dbus-kiosk-app-client-action', - 'variables': { - 'mock_output_file': 'include/kiosk-app/dbus-proxy-mocks.h', - 'proxy_output_file': 'include/kiosk-app/dbus-proxies.h', - }, - 'sources': [ - 'dbus_bindings/org.chromium.KioskAppService.dbus-xml', - ], - 'includes': ['../../../platform2/common-mk/generate-dbus-proxies.gypi'], - }], - }, - # The payload application component and common dependencies. - { - 'target_name': 'libpayload_consumer', - 'type': 'static_library', - 'dependencies': [ - 'update_metadata-protos', - ], - # TODO(deymo): Remove unused dependencies once we stop including files - # from the root directory. - 'variables': { - 'exported_deps': [ - 'libcrypto', - 'xz-embedded', - 'libbspatch', - 'libpuffpatch', - ], - 'deps': ['<@(exported_deps)'], - }, - 'all_dependent_settings': { - 'variables': { - 'deps': [ - '<@(exported_deps)', - ], - }, - }, - 'link_settings': { - 'variables': { - 'deps': [ - '<@(exported_deps)', - ], - }, - 'libraries': [ - '-lbz2', - '-lrt', - ], - }, - 'sources': [ - 'common/action_processor.cc', - 'common/boot_control_stub.cc', - 'common/clock.cc', - 'common/constants.cc', - 'common/cpu_limiter.cc', - 'common/error_code_utils.cc', - 'common/hash_calculator.cc', - 'common/http_common.cc', - 'common/http_fetcher.cc', - 'common/hwid_override.cc', - 'common/multi_range_http_fetcher.cc', - 'common/platform_constants_chromeos.cc', - 'common/prefs.cc', - 'common/proxy_resolver.cc', - 'common/subprocess.cc', - 'common/terminator.cc', - 'common/utils.cc', - 'payload_consumer/bzip_extent_writer.cc', - 'payload_consumer/cached_file_descriptor.cc', - 'payload_consumer/delta_performer.cc', - 'payload_consumer/download_action.cc', - 'payload_consumer/extent_reader.cc', - 'payload_consumer/extent_writer.cc', - 'payload_consumer/file_descriptor.cc', - 'payload_consumer/file_descriptor_utils.cc', - 'payload_consumer/file_writer.cc', - 'payload_consumer/filesystem_verifier_action.cc', - 'payload_consumer/install_plan.cc', - 'payload_consumer/mount_history.cc', - 'payload_consumer/payload_constants.cc', - 'payload_consumer/payload_metadata.cc', - 'payload_consumer/payload_verifier.cc', - 'payload_consumer/postinstall_runner_action.cc', - 'payload_consumer/verity_writer_stub.cc', - 'payload_consumer/xz_extent_writer.cc', - ], - 'conditions': [ - ['USE_mtd == 1', { - 'sources': [ - 'payload_consumer/mtd_file_descriptor.cc', - ], - 'link_settings': { - 'libraries': [ - '-lmtdutils', - ], - }, - }], - ], - }, - # The main daemon static_library with all the code used to check for updates - # with Omaha and expose a DBus daemon. - { - 'target_name': 'libupdate_engine', - 'type': 'static_library', - 'dependencies': [ - 'libpayload_consumer', - 'update_metadata-protos', - 'update_engine-dbus-adaptor', - ], - 'variables': { - 'exported_deps': [ - 'dbus-1', - 'expat', - 'libcurl', - 'libdebugd-client', - 'libmetrics-<(libbase_ver)', - 'libpower_manager-client', - 'libsession_manager-client', - 'libshill-client', - 'libssl', - 'libupdate_engine-client', - 'vboot_host', - ], - 'conditions':[ - ['USE_dlc == 1', { - 'exported_deps' : [ - 'libdlcservice-client', - ], - }], - ], - 'deps': ['<@(exported_deps)'], - }, - 'all_dependent_settings': { - 'variables': { - 'deps': [ - '<@(exported_deps)', - ], - }, - }, - 'link_settings': { - 'variables': { - 'deps': [ - '<@(exported_deps)', - ], - }, - 'libraries': [ - '-lbz2', - '-lpolicy-<(libbase_ver)', - '-lrootdev', - '-lrt', - ], - }, - 'sources': [ - 'boot_control_chromeos.cc', - 'certificate_checker.cc', - 'common_service.cc', - 'connection_manager.cc', - 'connection_utils.cc', - 'daemon.cc', - 'dbus_connection.cc', - 'dbus_service.cc', - 'hardware_chromeos.cc', - 'image_properties_chromeos.cc', - 'libcurl_http_fetcher.cc', - 'metrics_reporter_omaha.cc', - 'metrics_utils.cc', - 'omaha_request_action.cc', - 'omaha_request_params.cc', - 'omaha_response_handler_action.cc', - 'omaha_utils.cc', - 'p2p_manager.cc', - 'payload_state.cc', - 'power_manager_chromeos.cc', - 'real_system_state.cc', - 'shill_proxy.cc', - 'update_attempter.cc', - 'update_boot_flags_action.cc', - 'update_manager/boxed_value.cc', - 'update_manager/chromeos_policy.cc', - 'update_manager/default_policy.cc', - 'update_manager/enough_slots_ab_updates_policy_impl.cc', - 'update_manager/enterprise_device_policy_impl.cc', - 'update_manager/evaluation_context.cc', - 'update_manager/interactive_update_policy_impl.cc', - 'update_manager/next_update_check_policy_impl.cc', - 'update_manager/official_build_check_policy_impl.cc', - 'update_manager/out_of_box_experience_policy_impl.cc', - 'update_manager/policy.cc', - 'update_manager/policy_test_utils.cc', - 'update_manager/real_config_provider.cc', - 'update_manager/real_device_policy_provider.cc', - 'update_manager/real_random_provider.cc', - 'update_manager/real_shill_provider.cc', - 'update_manager/real_system_provider.cc', - 'update_manager/real_time_provider.cc', - 'update_manager/real_updater_provider.cc', - 'update_manager/staging_utils.cc', - 'update_manager/state_factory.cc', - 'update_manager/update_manager.cc', - 'update_manager/update_time_restrictions_policy_impl.cc', - 'update_manager/weekly_time.cc', - 'update_status_utils.cc', - ], - 'conditions': [ - ['USE_chrome_network_proxy == 1', { - 'sources': [ - 'chrome_browser_proxy_resolver.cc', - ], - }], - ['USE_chrome_kiosk_app == 1', { - 'dependencies': [ - 'update_engine-dbus-kiosk-app-client', - ], - }], - ['USE_dlc == 1', { - 'sources': [ - 'dlcservice_chromeos.cc', - ], - }], - ['USE_dlc == 0', { - 'sources': [ - 'common/dlcservice_stub.cc', - ], - }], - ], - }, - # update_engine daemon. - { - 'target_name': 'update_engine', - 'type': 'executable', - 'dependencies': [ - 'libupdate_engine', - ], - 'sources': [ - 'main.cc', - ], - }, - # update_engine client library. - { - 'target_name': 'libupdate_engine_client', - 'type': 'static_library', - 'variables': { - 'deps': [ - 'dbus-1', - 'libupdate_engine-client', - ], - }, - 'sources': [ - 'client_library/client.cc', - 'client_library/client_dbus.cc', - 'update_status_utils.cc', - ], - 'include_dirs': [ - 'client_library/include', - ], - }, - # update_engine console client. - { - 'target_name': 'update_engine_client', - 'type': 'executable', - 'dependencies': [ - 'libupdate_engine_client', - ], - 'sources': [ - 'common/error_code_utils.cc', - 'omaha_utils.cc', - 'update_engine_client.cc', - ], - }, - # server-side code. This is used for delta_generator and unittests but not - # for any client code. - { - 'target_name': 'libpayload_generator', - 'type': 'static_library', - 'dependencies': [ - 'libpayload_consumer', - 'update_metadata-protos', - ], - 'variables': { - 'exported_deps': [ - 'ext2fs', - 'libbsdiff', - 'libpuffdiff', - 'liblzma', - ], - 'deps': ['<@(exported_deps)'], - }, - 'all_dependent_settings': { - 'variables': { - 'deps': [ - '<@(exported_deps)', - ], - }, - }, - 'link_settings': { - 'variables': { - 'deps': [ - '<@(exported_deps)', - ], - }, - }, - 'sources': [ - 'common/file_fetcher.cc', - 'payload_generator/ab_generator.cc', - 'payload_generator/annotated_operation.cc', - 'payload_generator/blob_file_writer.cc', - 'payload_generator/block_mapping.cc', - 'payload_generator/boot_img_filesystem.cc', - 'payload_generator/bzip.cc', - 'payload_generator/cycle_breaker.cc', - 'payload_generator/deflate_utils.cc', - 'payload_generator/delta_diff_generator.cc', - 'payload_generator/delta_diff_utils.cc', - 'payload_generator/ext2_filesystem.cc', - 'payload_generator/extent_ranges.cc', - 'payload_generator/extent_utils.cc', - 'payload_generator/full_update_generator.cc', - 'payload_generator/graph_types.cc', - 'payload_generator/graph_utils.cc', - 'payload_generator/inplace_generator.cc', - 'payload_generator/mapfile_filesystem.cc', - 'payload_generator/payload_file.cc', - 'payload_generator/payload_generation_config_chromeos.cc', - 'payload_generator/payload_generation_config.cc', - 'payload_generator/payload_signer.cc', - 'payload_generator/raw_filesystem.cc', - 'payload_generator/squashfs_filesystem.cc', - 'payload_generator/tarjan.cc', - 'payload_generator/topological_sort.cc', - 'payload_generator/xz_chromeos.cc', - ], - }, - # server-side delta generator. - { - 'target_name': 'delta_generator', - 'type': 'executable', - 'dependencies': [ - 'libpayload_consumer', - 'libpayload_generator', - ], - 'link_settings': { - 'ldflags!': [ - '-pie', - ], - }, - 'sources': [ - 'payload_generator/generate_delta_main.cc', - ], - }, - { - 'target_name': 'update_engine_test_libs', - 'type': 'static_library', - 'variables': { - 'deps': [ - 'libshill-client-test', - ], - }, - 'dependencies': [ - 'libupdate_engine', - ], - 'includes': [ - '../../../platform2/common-mk/common_test.gypi', - ], - 'sources': [ - 'common/fake_prefs.cc', - 'common/mock_http_fetcher.cc', - 'common/test_utils.cc', - 'fake_shill_proxy.cc', - 'fake_system_state.cc', - 'payload_consumer/fake_file_descriptor.cc', - 'payload_generator/fake_filesystem.cc', - 'update_manager/umtest_utils.cc', - ], - }, - ], - 'conditions': [ - ['USE_test == 1', { - 'targets': [ - # Public keys used for unit testing. - { - 'target_name': 'update_engine-testkeys', - 'type': 'none', - 'variables': { - 'openssl_pem_in_dir': '.', - 'openssl_pem_out_dir': 'include/update_engine', - }, - 'sources': [ - 'unittest_key.pem', - 'unittest_key2.pem', - 'unittest_key_RSA4096.pem', - ], - 'includes': ['../../../platform2/common-mk/openssl_pem.gypi'], - }, - # Unpacks sample images used for testing. - { - 'target_name': 'update_engine-test_images', - 'type': 'none', - 'variables': { - 'image_out_dir': '.', - }, - 'sources': [ - 'sample_images/sample_images.tar.bz2', - ], - 'includes': ['tar_bunzip2.gypi'], - }, - # Test HTTP Server. - { - 'target_name': 'test_http_server', - 'type': 'executable', - 'sources': [ - 'common/http_common.cc', - 'test_http_server.cc', - ], - }, - # Test subprocess helper. - { - 'target_name': 'test_subprocess', - 'type': 'executable', - 'sources': [ - 'test_subprocess.cc', - ], - }, - # Main unittest file. - { - 'target_name': 'update_engine_unittests', - 'type': 'executable', - 'variables': { - 'deps': [ - 'libbrillo-test-<(libbase_ver)', - 'libchrome-test-<(libbase_ver)', - 'libdebugd-client-test', - 'libpower_manager-client-test', - 'libsession_manager-client-test', - 'libshill-client-test', - ], - }, - 'dependencies': [ - 'libupdate_engine', - 'libpayload_generator', - 'update_engine_test_libs', - ], - 'sources': [ - 'boot_control_chromeos_unittest.cc', - 'certificate_checker_unittest.cc', - 'common/action_pipe_unittest.cc', - 'common/action_processor_unittest.cc', - 'common/action_unittest.cc', - 'common/cpu_limiter_unittest.cc', - 'common/hash_calculator_unittest.cc', - 'common/http_fetcher_unittest.cc', - 'common/hwid_override_unittest.cc', - 'common/prefs_unittest.cc', - 'common/proxy_resolver_unittest.cc', - 'common/subprocess_unittest.cc', - 'common/terminator_unittest.cc', - 'common/utils_unittest.cc', - 'common_service_unittest.cc', - 'connection_manager_unittest.cc', - 'hardware_chromeos_unittest.cc', - 'image_properties_chromeos_unittest.cc', - 'metrics_reporter_omaha_unittest.cc', - 'metrics_utils_unittest.cc', - 'omaha_request_action_unittest.cc', - 'omaha_request_params_unittest.cc', - 'omaha_response_handler_action_unittest.cc', - 'omaha_utils_unittest.cc', - 'p2p_manager_unittest.cc', - 'payload_consumer/bzip_extent_writer_unittest.cc', - 'payload_consumer/cached_file_descriptor_unittest.cc', - 'payload_consumer/delta_performer_integration_test.cc', - 'payload_consumer/delta_performer_unittest.cc', - 'payload_consumer/download_action_unittest.cc', - 'payload_consumer/extent_reader_unittest.cc', - 'payload_consumer/extent_writer_unittest.cc', - 'payload_consumer/file_descriptor_utils_unittest.cc', - 'payload_consumer/file_writer_unittest.cc', - 'payload_consumer/filesystem_verifier_action_unittest.cc', - 'payload_consumer/postinstall_runner_action_unittest.cc', - 'payload_consumer/xz_extent_writer_unittest.cc', - '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/cycle_breaker_unittest.cc', - 'payload_generator/deflate_utils_unittest.cc', - 'payload_generator/delta_diff_utils_unittest.cc', - 'payload_generator/ext2_filesystem_unittest.cc', - 'payload_generator/extent_ranges_unittest.cc', - 'payload_generator/extent_utils_unittest.cc', - 'payload_generator/full_update_generator_unittest.cc', - 'payload_generator/graph_utils_unittest.cc', - 'payload_generator/inplace_generator_unittest.cc', - 'payload_generator/mapfile_filesystem_unittest.cc', - 'payload_generator/payload_file_unittest.cc', - 'payload_generator/payload_generation_config_unittest.cc', - 'payload_generator/payload_signer_unittest.cc', - 'payload_generator/squashfs_filesystem_unittest.cc', - 'payload_generator/tarjan_unittest.cc', - 'payload_generator/topological_sort_unittest.cc', - 'payload_generator/zip_unittest.cc', - 'payload_state_unittest.cc', - 'testrunner.cc', - 'update_attempter_unittest.cc', - 'update_boot_flags_action_unittest.cc', - 'update_manager/boxed_value_unittest.cc', - 'update_manager/chromeos_policy_unittest.cc', - 'update_manager/evaluation_context_unittest.cc', - 'update_manager/generic_variables_unittest.cc', - 'update_manager/prng_unittest.cc', - 'update_manager/real_device_policy_provider_unittest.cc', - 'update_manager/real_random_provider_unittest.cc', - 'update_manager/real_shill_provider_unittest.cc', - 'update_manager/real_system_provider_unittest.cc', - 'update_manager/real_time_provider_unittest.cc', - 'update_manager/real_updater_provider_unittest.cc', - 'update_manager/staging_utils_unittest.cc', - 'update_manager/update_manager_unittest.cc', - 'update_manager/update_time_restrictions_policy_impl_unittest.cc', - 'update_manager/variable_unittest.cc', - 'update_manager/weekly_time_unittest.cc', - ], - }, - ], - }], - # Fuzzer target. - ['USE_fuzzer == 1', { - 'targets': [ - { - 'target_name': 'update_engine_omaha_request_action_fuzzer', - 'type': 'executable', - 'variables': { - 'deps': [ - 'libbrillo-test-<(libbase_ver)', - 'libchrome-test-<(libbase_ver)', - ], - }, - 'includes': [ - '../../../platform2/common-mk/common_fuzzer.gypi', - ], - 'dependencies': [ - 'libupdate_engine', - 'update_engine_test_libs', - ], - 'sources': [ - 'omaha_request_action_fuzzer.cc', - ], - }, - ], - }], - ], -} diff --git a/update_manager/android_things_policy.cc b/update_manager/android_things_policy.cc deleted file mode 100644 index 4afcf12e..00000000 --- a/update_manager/android_things_policy.cc +++ /dev/null @@ -1,182 +0,0 @@ -// -// Copyright (C) 2017 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/update_manager/android_things_policy.h" - -#include <string> -#include <vector> - -#include <base/logging.h> -#include <base/time/time.h> - -#include "update_engine/update_manager/api_restricted_downloads_policy_impl.h" -#include "update_engine/update_manager/enough_slots_ab_updates_policy_impl.h" -#include "update_engine/update_manager/interactive_update_policy_impl.h" -#include "update_engine/update_manager/official_build_check_policy_impl.h" - -using base::Time; -using chromeos_update_engine::ErrorCode; -using std::string; -using std::vector; - -namespace chromeos_update_manager { - -const NextUpdateCheckPolicyConstants - AndroidThingsPolicy::kNextUpdateCheckPolicyConstants = { - .timeout_initial_interval = 7 * 60, - .timeout_periodic_interval = 5 * 60 * 60, - .timeout_max_backoff_interval = 26 * 60 * 60, - .timeout_regular_fuzz = 10 * 60, - .attempt_backoff_max_interval_in_days = 16, - .attempt_backoff_fuzz_in_hours = 12, -}; - -EvalStatus AndroidThingsPolicy::UpdateCheckAllowed( - EvaluationContext* ec, - State* state, - string* error, - UpdateCheckParams* result) const { - // Set the default return values. - result->updates_enabled = true; - result->target_channel.clear(); - result->target_version_prefix.clear(); - result->rollback_allowed = false; - result->rollback_allowed_milestones = -1; - result->interactive = false; - - // Build a list of policies to consult. Note that each policy may modify the - // result structure, even if it signals kContinue. - EnoughSlotsAbUpdatesPolicyImpl enough_slots_ab_updates_policy; - OnlyUpdateOfficialBuildsPolicyImpl only_update_official_builds_policy; - InteractiveUpdatePolicyImpl interactive_update_policy; - NextUpdateCheckTimePolicyImpl next_update_check_time_policy( - kNextUpdateCheckPolicyConstants); - - vector<Policy const*> policies_to_consult = { - // Do not perform any updates if there are not enough slots to do - // A/B updates - &enough_slots_ab_updates_policy, - - // Check to see if an interactive update was requested. - &interactive_update_policy, - - // Unofficial builds should not perform periodic update checks. - &only_update_official_builds_policy, - - // Ensure that periodic update checks are timed properly. - &next_update_check_time_policy, - }; - - // Now that the list of policy implementations, and the order to consult them, - // as been setup, do that. If none of the policies make a definitive - // decisions about whether or not to check for updates, then allow the update - // check to happen. - EvalStatus status = ConsultPolicies(policies_to_consult, - &Policy::UpdateCheckAllowed, - ec, - state, - error, - result); - if (status != EvalStatus::kContinue) { - return status; - } else { - // It is time to check for an update. - LOG(INFO) << "Allowing update check."; - return EvalStatus::kSucceeded; - } -} - -// Uses the |UpdateRestrictions| to determine if the download and apply can -// occur at this time. -EvalStatus AndroidThingsPolicy::UpdateCanBeApplied( - EvaluationContext* ec, - State* state, - string* error, - ErrorCode* result, - chromeos_update_engine::InstallPlan* install_plan) const { - // Build a list of policies to consult. Note that each policy may modify the - // result structure, even if it signals kContinue. - ApiRestrictedDownloadsPolicyImpl api_restricted_downloads_policy; - - vector<Policy const*> policies_to_consult = { - // Do not apply the update if all updates are restricted by the API. - &api_restricted_downloads_policy, - }; - - // Now that the list of policy implementations, and the order to consult them, - // as been setup, do that. If none of the policies make a definitive - // decisions about whether or not to check for updates, then allow the update - // check to happen. - EvalStatus status = ConsultPolicies(policies_to_consult, - &Policy::UpdateCanBeApplied, - ec, - state, - error, - result, - install_plan); - if (EvalStatus::kContinue != status) { - return status; - } else { - // The update can proceed. - LOG(INFO) << "Allowing update to be applied."; - *result = ErrorCode::kSuccess; - return EvalStatus::kSucceeded; - } -} - -// Always returns |EvalStatus::kSucceeded| -EvalStatus AndroidThingsPolicy::UpdateCanStart(EvaluationContext* ec, - State* state, - string* error, - UpdateDownloadParams* result, - UpdateState update_state) const { - // Update is good to go. - result->update_can_start = true; - return EvalStatus::kSucceeded; -} - -// Always returns |EvalStatus::kSucceeded| -EvalStatus AndroidThingsPolicy::UpdateDownloadAllowed(EvaluationContext* ec, - State* state, - string* error, - bool* result) const { - // By default, we allow updates. - *result = true; - return EvalStatus::kSucceeded; -} - -// P2P is always disabled. Returns |result|==|false| and -// |EvalStatus::kSucceeded| -EvalStatus AndroidThingsPolicy::P2PEnabled(EvaluationContext* ec, - State* state, - string* error, - bool* result) const { - *result = false; - return EvalStatus::kSucceeded; -} - -// This will return immediately with |EvalStatus::kSucceeded| and set -// |result|==|false| -EvalStatus AndroidThingsPolicy::P2PEnabledChanged(EvaluationContext* ec, - State* state, - string* error, - bool* result, - bool prev_result) const { - *result = false; - return EvalStatus::kSucceeded; -} - -} // namespace chromeos_update_manager diff --git a/update_manager/android_things_policy.h b/update_manager/android_things_policy.h deleted file mode 100644 index 9fd8bc44..00000000 --- a/update_manager/android_things_policy.h +++ /dev/null @@ -1,92 +0,0 @@ -// -// Copyright (C) 2017 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_UPDATE_MANAGER_ANDROID_THINGS_POLICY_H_ -#define UPDATE_ENGINE_UPDATE_MANAGER_ANDROID_THINGS_POLICY_H_ - -#include <string> - -#include "update_engine/update_manager/next_update_check_policy_impl.h" -#include "update_engine/update_manager/policy_utils.h" - -namespace chromeos_update_manager { - -// AndroidThingsPolicy implements the policy-related logic used in -// AndroidThings. -class AndroidThingsPolicy : public Policy { - public: - AndroidThingsPolicy() = default; - ~AndroidThingsPolicy() override = default; - - // Policy overrides. - EvalStatus UpdateCheckAllowed(EvaluationContext* ec, - State* state, - std::string* error, - UpdateCheckParams* result) const override; - - // Uses the |UpdateRestrictions| to determine if the download and apply can - // occur at this time. - EvalStatus UpdateCanBeApplied( - EvaluationContext* ec, - State* state, - std::string* error, - chromeos_update_engine::ErrorCode* result, - chromeos_update_engine::InstallPlan* install_plan) const override; - - // Always returns |EvalStatus::kSucceeded| - EvalStatus UpdateCanStart(EvaluationContext* ec, - State* state, - std::string* error, - UpdateDownloadParams* result, - UpdateState update_state) const override; - - // Always returns |EvalStatus::kSucceeded| - EvalStatus UpdateDownloadAllowed(EvaluationContext* ec, - State* state, - std::string* error, - bool* result) const override; - - // P2P is always disabled. Returns |result|==|false| and - // |EvalStatus::kSucceeded| - EvalStatus P2PEnabled(EvaluationContext* ec, - State* state, - std::string* error, - bool* result) const override; - - // This will return immediately with |EvalStatus::kSucceeded| and set - // |result|==|false| - EvalStatus P2PEnabledChanged(EvaluationContext* ec, - State* state, - std::string* error, - bool* result, - bool prev_result) const override; - - protected: - // Policy override. - std::string PolicyName() const override { return "AndroidThingsPolicy"; } - - private: - friend class UmAndroidThingsPolicyTest; - FRIEND_TEST(UmAndroidThingsPolicyTest, UpdateCheckAllowedWaitsForTheTimeout); - - static const NextUpdateCheckPolicyConstants kNextUpdateCheckPolicyConstants; - - DISALLOW_COPY_AND_ASSIGN(AndroidThingsPolicy); -}; - -} // namespace chromeos_update_manager - -#endif // UPDATE_ENGINE_UPDATE_MANAGER_ANDROID_THINGS_POLICY_H_ diff --git a/update_manager/android_things_policy_unittest.cc b/update_manager/android_things_policy_unittest.cc deleted file mode 100644 index 6961efcb..00000000 --- a/update_manager/android_things_policy_unittest.cc +++ /dev/null @@ -1,188 +0,0 @@ -// -// Copyright (C) 2017 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/update_manager/android_things_policy.h" - -#include <memory> - -#include "update_engine/update_manager/next_update_check_policy_impl.h" -#include "update_engine/update_manager/policy_test_utils.h" - -using base::Time; -using base::TimeDelta; -using chromeos_update_engine::ErrorCode; -using chromeos_update_engine::InstallPlan; - -namespace chromeos_update_manager { - -class UmAndroidThingsPolicyTest : public UmPolicyTestBase { - protected: - UmAndroidThingsPolicyTest() { - policy_ = std::make_unique<AndroidThingsPolicy>(); - } - - void SetUpDefaultState() override { - UmPolicyTestBase::SetUpDefaultState(); - - // For the purpose of the tests, this is an official build - fake_state_.system_provider()->var_is_official_build()->reset( - new bool(true)); - // NOLINTNEXTLINE(readability/casting) - fake_state_.system_provider()->var_num_slots()->reset(new unsigned int(2)); - } - - // Configures the policy to return a desired value from UpdateCheckAllowed by - // faking the current wall clock time as needed. Restores the default state. - // This is used when testing policies that depend on this one. - virtual void SetUpdateCheckAllowed(bool allow_check) { - Time next_update_check; - CallMethodWithContext(&NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime, - &next_update_check, - AndroidThingsPolicy::kNextUpdateCheckPolicyConstants); - SetUpDefaultState(); - Time curr_time = next_update_check; - if (allow_check) - curr_time += TimeDelta::FromSeconds(1); - else - curr_time -= TimeDelta::FromSeconds(1); - fake_clock_.SetWallclockTime(curr_time); - } -}; - -TEST_F(UmAndroidThingsPolicyTest, UpdateCheckAllowedWaitsForTheTimeout) { - // We get the next update_check timestamp from the policy's private method - // and then we check the public method respects that value on the normal - // case. - Time next_update_check; - Time last_checked_time = - fake_clock_.GetWallclockTime() + TimeDelta::FromMinutes(1234); - - LOG(INFO) << "last_checked_time: " << last_checked_time; - fake_state_.updater_provider()->var_last_checked_time()->reset( - new Time(last_checked_time)); - CallMethodWithContext(&NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime, - &next_update_check, - AndroidThingsPolicy::kNextUpdateCheckPolicyConstants); - LOG(INFO) << "Next check allowed at: " << next_update_check; - - // Check that the policy blocks until the next_update_check is reached. - SetUpDefaultClock(); - SetUpDefaultState(); - fake_state_.updater_provider()->var_last_checked_time()->reset( - new Time(last_checked_time)); - fake_clock_.SetWallclockTime(next_update_check - TimeDelta::FromSeconds(1)); - - UpdateCheckParams result; - ExpectPolicyStatus( - EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result); - - SetUpDefaultClock(); - SetUpDefaultState(); - fake_state_.updater_provider()->var_last_checked_time()->reset( - new Time(last_checked_time)); - fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1)); - ExpectPolicyStatus( - EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result); - EXPECT_TRUE(result.updates_enabled); - EXPECT_FALSE(result.interactive); -} - -TEST_F(UmAndroidThingsPolicyTest, - UpdateCheckAllowedUpdatesDisabledForUnofficialBuilds) { - // UpdateCheckAllowed should return kAskMeAgainLater if this is an unofficial - // build; we don't want periodic update checks on developer images. - - fake_state_.system_provider()->var_is_official_build()->reset( - new bool(false)); - - UpdateCheckParams result; - ExpectPolicyStatus( - EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result); -} - -TEST_F(UmAndroidThingsPolicyTest, - UpdateCheckAllowedUpdatesDisabledWhenNotEnoughSlotsAbUpdates) { - // UpdateCheckAllowed should return false (kSucceeded) if the image booted - // without enough slots to do A/B updates. - - // NOLINTNEXTLINE(readability/casting) - fake_state_.system_provider()->var_num_slots()->reset(new unsigned int(1)); - - UpdateCheckParams result; - ExpectPolicyStatus( - EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result); - EXPECT_FALSE(result.updates_enabled); -} - -TEST_F(UmAndroidThingsPolicyTest, - UpdateCheckAllowedForcedUpdateRequestedInteractive) { - // UpdateCheckAllowed should return true because a forced update request was - // signaled for an interactive update. - - SetUpdateCheckAllowed(true); - fake_state_.updater_provider()->var_forced_update_requested()->reset( - new UpdateRequestStatus(UpdateRequestStatus::kInteractive)); - - UpdateCheckParams result; - ExpectPolicyStatus( - EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result); - EXPECT_TRUE(result.updates_enabled); - EXPECT_TRUE(result.interactive); -} - -TEST_F(UmAndroidThingsPolicyTest, - UpdateCheckAllowedForcedUpdateRequestedPeriodic) { - // UpdateCheckAllowed should return true because a forced update request was - // signaled for a periodic check. - - SetUpdateCheckAllowed(true); - fake_state_.updater_provider()->var_forced_update_requested()->reset( - new UpdateRequestStatus(UpdateRequestStatus::kPeriodic)); - - UpdateCheckParams result; - ExpectPolicyStatus( - EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result); - EXPECT_TRUE(result.updates_enabled); - EXPECT_FALSE(result.interactive); -} - -TEST_F(UmAndroidThingsPolicyTest, UpdateCanBeAppliedOk) { - // UpdateCanBeApplied should return kSucceeded in the base case - - InstallPlan plan; - ErrorCode result; - ExpectPolicyStatus( - EvalStatus::kSucceeded, &Policy::UpdateCanBeApplied, &result, &plan); - - EXPECT_EQ(ErrorCode::kSuccess, result); -} - -TEST_F(UmAndroidThingsPolicyTest, UpdateCanBeAppliedRestricted) { - // UpdateCanBeApplied should return kOmahaUpdateDeferredPerPolicy in - // when the restricted flag is set in the Updater. - - fake_state_.updater_provider()->var_update_restrictions()->reset( - new UpdateRestrictions(UpdateRestrictions::kRestrictDownloading)); - - InstallPlan plan; - ErrorCode result; - ExpectPolicyStatus( - EvalStatus::kSucceeded, &Policy::UpdateCanBeApplied, &result, &plan); - - EXPECT_EQ(ErrorCode::kOmahaUpdateDeferredPerPolicy, result); -} - -} // namespace chromeos_update_manager diff --git a/update_manager/boxed_value.cc b/update_manager/boxed_value.cc index cee1ece8..907eb959 100644 --- a/update_manager/boxed_value.cc +++ b/update_manager/boxed_value.cc @@ -23,9 +23,10 @@ #include <base/strings/string_number_conversions.h> #include <base/time/time.h> +#include <base/version.h> +#include "update_engine/common/connection_utils.h" #include "update_engine/common/utils.h" -#include "update_engine/connection_utils.h" #include "update_engine/update_manager/rollback_prefs.h" #include "update_engine/update_manager/shill_provider.h" #include "update_engine/update_manager/updater_provider.h" @@ -51,41 +52,25 @@ string BoxedValue::ValuePrinter<string>(const void* value) { template <> string BoxedValue::ValuePrinter<int>(const void* value) { const int* val = reinterpret_cast<const int*>(value); -#if BASE_VER < 576279 - return base::IntToString(*val); -#else return base::NumberToString(*val); -#endif } template <> string BoxedValue::ValuePrinter<unsigned int>(const void* value) { const unsigned int* val = reinterpret_cast<const unsigned int*>(value); -#if BASE_VER < 576279 - return base::UintToString(*val); -#else return base::NumberToString(*val); -#endif } template <> string BoxedValue::ValuePrinter<int64_t>(const void* value) { const int64_t* val = reinterpret_cast<const int64_t*>(value); -#if BASE_VER < 576279 - return base::Int64ToString(*val); -#else return base::NumberToString(*val); -#endif } template <> string BoxedValue::ValuePrinter<uint64_t>(const void* value) { const uint64_t* val = reinterpret_cast<const uint64_t*>(value); -#if BASE_VER < 576279 - return base::Uint64ToString(*val); -#else return base::NumberToString(*val); -#endif } template <> @@ -97,11 +82,7 @@ string BoxedValue::ValuePrinter<bool>(const void* value) { template <> string BoxedValue::ValuePrinter<double>(const void* value) { const double* val = reinterpret_cast<const double*>(value); -#if BASE_VER < 576279 - return base::DoubleToString(*val); -#else return base::NumberToString(*val); -#endif } template <> @@ -167,8 +148,6 @@ string BoxedValue::ValuePrinter<RollbackToTargetVersion>(const void* value) { return "Rollback and powerwash"; case RollbackToTargetVersion::kRollbackAndRestoreIfPossible: return "Rollback and restore if possible"; - case RollbackToTargetVersion::kRollbackOnlyIfRestorePossible: - return "Rollback only if restore is possible"; case RollbackToTargetVersion::kMaxValue: NOTREACHED(); return "Max value"; @@ -199,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"; @@ -254,4 +235,30 @@ string BoxedValue::ValuePrinter<WeeklyTimeIntervalVector>(const void* value) { return retval; } +template <> +string BoxedValue::ValuePrinter<ChannelDowngradeBehavior>(const void* value) { + const ChannelDowngradeBehavior* val = + reinterpret_cast<const ChannelDowngradeBehavior*>(value); + switch (*val) { + case ChannelDowngradeBehavior::kUnspecified: + return "Unspecified"; + case ChannelDowngradeBehavior::kWaitForVersionToCatchUp: + return "Wait for the target channel to catch up"; + case ChannelDowngradeBehavior::kRollback: + return "Roll back and powerwash on channel downgrade"; + case ChannelDowngradeBehavior::kAllowUserToConfigure: + return "User decides on channel downgrade behavior"; + } + NOTREACHED(); + return "Unknown"; +} + +template <> +string BoxedValue::ValuePrinter<base::Version>(const void* value) { + const base::Version* val = reinterpret_cast<const base::Version*>(value); + if (val->IsValid()) + return val->GetString(); + return "Unknown"; +} + } // namespace chromeos_update_manager diff --git a/update_manager/boxed_value_unittest.cc b/update_manager/boxed_value_unittest.cc index 2fa94ec0..5b87a7b6 100644 --- a/update_manager/boxed_value_unittest.cc +++ b/update_manager/boxed_value_unittest.cc @@ -168,11 +168,6 @@ TEST(UmBoxedValueTest, ConnectionTypeToString) { BoxedValue(new ConnectionType(ConnectionType::kEthernet)).ToString()); EXPECT_EQ("wifi", BoxedValue(new ConnectionType(ConnectionType::kWifi)).ToString()); - EXPECT_EQ("wimax", - BoxedValue(new ConnectionType(ConnectionType::kWimax)).ToString()); - EXPECT_EQ( - "bluetooth", - BoxedValue(new ConnectionType(ConnectionType::kBluetooth)).ToString()); EXPECT_EQ( "cellular", BoxedValue(new ConnectionType(ConnectionType::kCellular)).ToString()); @@ -215,18 +210,13 @@ TEST(UmBoxedValueTest, RollbackToTargetVersionToString) { BoxedValue(new RollbackToTargetVersion( RollbackToTargetVersion::kRollbackAndRestoreIfPossible)) .ToString()); - EXPECT_EQ( - "Rollback only if restore is possible", - BoxedValue(new RollbackToTargetVersion( - RollbackToTargetVersion::kRollbackOnlyIfRestorePossible)) - .ToString()); } TEST(UmBoxedValueTest, SetConnectionTypeToString) { set<ConnectionType>* set1 = new set<ConnectionType>; - set1->insert(ConnectionType::kWimax); + set1->insert(ConnectionType::kCellular); set1->insert(ConnectionType::kEthernet); - EXPECT_EQ("ethernet,wimax", BoxedValue(set1).ToString()); + EXPECT_EQ("ethernet,cellular", BoxedValue(set1).ToString()); set<ConnectionType>* set2 = new set<ConnectionType>; set2->insert(ConnectionType::kWifi); diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc index 1fa86368..24b8293e 100644 --- a/update_manager/chromeos_policy.cc +++ b/update_manager/chromeos_policy.cc @@ -17,6 +17,7 @@ #include "update_engine/update_manager/chromeos_policy.h" #include <algorithm> +#include <memory> #include <set> #include <string> #include <vector> @@ -48,6 +49,7 @@ using std::get; using std::min; using std::set; using std::string; +using std::unique_ptr; using std::vector; namespace { @@ -154,6 +156,7 @@ bool HandleErrorCode(ErrorCode err_code, int* url_num_error_p) { 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) << " (" << static_cast<int>(err_code) << ")"; @@ -190,6 +193,10 @@ bool IsUrlUsable(const string& url, bool http_allowed) { namespace chromeos_update_manager { +unique_ptr<Policy> GetSystemPolicy() { + return std::make_unique<ChromeOSPolicy>(); +} + const NextUpdateCheckPolicyConstants ChromeOSPolicy::kNextUpdateCheckPolicyConstants = { .timeout_initial_interval = 7 * 60, @@ -210,10 +217,13 @@ EvalStatus ChromeOSPolicy::UpdateCheckAllowed(EvaluationContext* ec, // Set the default return values. result->updates_enabled = true; result->target_channel.clear(); + result->lts_tag.clear(); result->target_version_prefix.clear(); result->rollback_allowed = false; result->rollback_allowed_milestones = -1; + result->rollback_on_channel_downgrade = false; result->interactive = false; + result->quick_fix_build_token.clear(); EnoughSlotsAbUpdatesPolicyImpl enough_slots_ab_updates_policy; EnterpriseDevicePolicyImpl enterprise_device_policy; @@ -456,92 +466,6 @@ EvalStatus ChromeOSPolicy::UpdateCanStart( return EvalStatus::kSucceeded; } -// TODO(garnold) Logic in this method is based on -// ConnectionManager::IsUpdateAllowedOver(); be sure to deprecate the latter. -// -// TODO(garnold) The current logic generally treats the list of allowed -// connections coming from the device policy as a whitelist, meaning that it -// can only be used for enabling connections, but not disable them. Further, -// certain connection types (like Bluetooth) cannot be enabled even by policy. -// In effect, the only thing that device policy can change is to enable -// updates over a cellular network (disabled by default). We may want to -// revisit this semantics, allowing greater flexibility in defining specific -// permissions over all types of networks. -EvalStatus ChromeOSPolicy::UpdateDownloadAllowed(EvaluationContext* ec, - State* state, - string* error, - bool* result) const { - // Get the current connection type. - ShillProvider* const shill_provider = state->shill_provider(); - const ConnectionType* conn_type_p = - ec->GetValue(shill_provider->var_conn_type()); - POLICY_CHECK_VALUE_AND_FAIL(conn_type_p, error); - ConnectionType conn_type = *conn_type_p; - - // If we're tethering, treat it as a cellular connection. - if (conn_type != ConnectionType::kCellular) { - const ConnectionTethering* conn_tethering_p = - ec->GetValue(shill_provider->var_conn_tethering()); - POLICY_CHECK_VALUE_AND_FAIL(conn_tethering_p, error); - if (*conn_tethering_p == ConnectionTethering::kConfirmed) - conn_type = ConnectionType::kCellular; - } - - // By default, we allow updates for all connection types, with exceptions as - // noted below. This also determines whether a device policy can override the - // default. - *result = true; - bool device_policy_can_override = false; - switch (conn_type) { - case ConnectionType::kBluetooth: - *result = false; - break; - - case ConnectionType::kCellular: - *result = false; - device_policy_can_override = true; - break; - - case ConnectionType::kUnknown: - if (error) - *error = "Unknown connection type"; - return EvalStatus::kFailed; - - default: - break; // Nothing to do. - } - - // If update is allowed, we're done. - if (*result) - return EvalStatus::kSucceeded; - - // Check whether the device policy specifically allows this connection. - if (device_policy_can_override) { - DevicePolicyProvider* const dp_provider = state->device_policy_provider(); - const bool* device_policy_is_loaded_p = - ec->GetValue(dp_provider->var_device_policy_is_loaded()); - if (device_policy_is_loaded_p && *device_policy_is_loaded_p) { - const set<ConnectionType>* allowed_conn_types_p = - ec->GetValue(dp_provider->var_allowed_connection_types_for_update()); - if (allowed_conn_types_p) { - if (allowed_conn_types_p->count(conn_type)) { - *result = true; - return EvalStatus::kSucceeded; - } - } else if (conn_type == ConnectionType::kCellular) { - // Local user settings can allow updates over cellular iff a policy was - // loaded but no allowed connections were specified in it. - const bool* update_over_cellular_allowed_p = - ec->GetValue(state->updater_provider()->var_cellular_enabled()); - if (update_over_cellular_allowed_p && *update_over_cellular_allowed_p) - *result = true; - } - } - } - - return (*result ? EvalStatus::kSucceeded : EvalStatus::kAskMeAgainLater); -} - EvalStatus ChromeOSPolicy::P2PEnabled(EvaluationContext* ec, State* state, string* error, @@ -560,8 +484,9 @@ EvalStatus ChromeOSPolicy::P2PEnabled(EvaluationContext* ec, if (policy_au_p2p_enabled_p) { enabled = *policy_au_p2p_enabled_p; } else { - const string* policy_owner_p = ec->GetValue(dp_provider->var_owner()); - if (!policy_owner_p || policy_owner_p->empty()) + const bool* policy_has_owner_p = + ec->GetValue(dp_provider->var_has_owner()); + if (!policy_has_owner_p || !*policy_has_owner_p) enabled = true; } } @@ -595,7 +520,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. @@ -667,7 +591,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/chromeos_policy.h b/update_manager/chromeos_policy.h index ded51646..3c196dac 100644 --- a/update_manager/chromeos_policy.h +++ b/update_manager/chromeos_policy.h @@ -72,11 +72,6 @@ class ChromeOSPolicy : public Policy { UpdateDownloadParams* result, UpdateState update_state) const override; - EvalStatus UpdateDownloadAllowed(EvaluationContext* ec, - State* state, - std::string* error, - bool* result) const override; - EvalStatus P2PEnabled(EvaluationContext* ec, State* state, std::string* error, diff --git a/update_manager/chromeos_policy_unittest.cc b/update_manager/chromeos_policy_unittest.cc index 5341ebb3..5bd416d3 100644 --- a/update_manager/chromeos_policy_unittest.cc +++ b/update_manager/chromeos_policy_unittest.cc @@ -109,7 +109,7 @@ class UmChromeOSPolicyTest : public UmPolicyTestBase { curr_time += TimeDelta::FromSeconds(1); else curr_time -= TimeDelta::FromSeconds(1); - fake_clock_.SetWallclockTime(curr_time); + fake_clock_->SetWallclockTime(curr_time); } // Sets the policies required for a kiosk app to control Chrome OS version: @@ -180,7 +180,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout) { // case. Time next_update_check; Time last_checked_time = - fake_clock_.GetWallclockTime() + TimeDelta::FromMinutes(1234); + fake_clock_->GetWallclockTime() + TimeDelta::FromMinutes(1234); fake_state_.updater_provider()->var_last_checked_time()->reset( new Time(last_checked_time)); @@ -195,7 +195,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout) { SetUpDefaultState(); fake_state_.updater_provider()->var_last_checked_time()->reset( new Time(last_checked_time)); - fake_clock_.SetWallclockTime(next_update_check - TimeDelta::FromSeconds(1)); + fake_clock_->SetWallclockTime(next_update_check - TimeDelta::FromSeconds(1)); ExpectPolicyStatus( EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result); @@ -203,7 +203,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout) { SetUpDefaultState(); fake_state_.updater_provider()->var_last_checked_time()->reset( new Time(last_checked_time)); - fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1)); + fake_clock_->SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1)); ExpectPolicyStatus( EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result); EXPECT_TRUE(result.updates_enabled); @@ -216,7 +216,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForOOBE) { // Ensure that update is not allowed even if wait period is satisfied. Time next_update_check; Time last_checked_time = - fake_clock_.GetWallclockTime() + TimeDelta::FromMinutes(1234); + fake_clock_->GetWallclockTime() + TimeDelta::FromMinutes(1234); fake_state_.updater_provider()->var_last_checked_time()->reset( new Time(last_checked_time)); @@ -228,7 +228,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForOOBE) { SetUpDefaultState(); fake_state_.updater_provider()->var_last_checked_time()->reset( new Time(last_checked_time)); - fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1)); + fake_clock_->SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1)); fake_state_.system_provider()->var_is_oobe_complete()->reset(new bool(false)); UpdateCheckParams result; @@ -240,7 +240,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForOOBE) { SetUpDefaultState(); fake_state_.updater_provider()->var_last_checked_time()->reset( new Time(last_checked_time)); - fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1)); + fake_clock_->SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1)); ExpectPolicyStatus( EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result); EXPECT_TRUE(result.updates_enabled); @@ -262,6 +262,10 @@ TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWithAttributes) { new bool(false)); fake_state_.device_policy_provider()->var_release_channel()->reset( new string("foo-channel")); + fake_state_.device_policy_provider()->var_release_lts_tag()->reset( + new string("foo-hint")); + fake_state_.device_policy_provider()->var_quick_fix_build_token()->reset( + new string("foo-token")); UpdateCheckParams result; ExpectPolicyStatus( @@ -270,6 +274,8 @@ TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWithAttributes) { EXPECT_EQ("1.2", result.target_version_prefix); EXPECT_EQ(5, result.rollback_allowed_milestones); EXPECT_EQ("foo-channel", result.target_channel); + EXPECT_EQ("foo-hint", result.lts_tag); + EXPECT_EQ("foo-token", result.quick_fix_build_token); EXPECT_FALSE(result.interactive); } @@ -284,12 +290,6 @@ TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedRollbackAndRestoreIfPossible) { true, RollbackToTargetVersion::kRollbackAndRestoreIfPossible)); } -TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedRollbackOnlyIfRestorePossible) { - // We're not allowed to do rollback until we support data save and restore. - EXPECT_FALSE(TestRollbackAllowed( - true, RollbackToTargetVersion::kRollbackOnlyIfRestorePossible)); -} - TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedRollbackDisabled) { EXPECT_FALSE(TestRollbackAllowed(true, RollbackToTargetVersion::kDisabled)); } @@ -344,6 +344,26 @@ TEST_F(UmChromeOSPolicyTest, EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result); } +TEST_F(UmChromeOSPolicyTest, TestUpdateCheckIntervalTimeout) { + fake_state_.updater_provider() + ->var_test_update_check_interval_timeout() + ->reset(new int64_t(10)); + fake_state_.system_provider()->var_is_official_build()->reset( + new bool(false)); + + // The first time, update should not be allowed. + UpdateCheckParams result; + ExpectPolicyStatus( + EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result); + + // After moving the time forward more than the update check interval, it + // should now allow for update. + fake_clock_->SetWallclockTime(fake_clock_->GetWallclockTime() + + TimeDelta::FromSeconds(11)); + ExpectPolicyStatus( + EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result); +} + TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedUpdatesDisabledWhenNotEnoughSlotsAbUpdates) { // UpdateCheckAllowed should return false (kSucceeded) if the image booted @@ -552,7 +572,7 @@ TEST_F(UmChromeOSPolicyTest, SetUpdateCheckAllowed(false); - const Time curr_time = fake_clock_.GetWallclockTime(); + const Time curr_time = fake_clock_->GetWallclockTime(); UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10)); update_state.download_errors_max = 1; update_state.download_errors.emplace_back( @@ -582,7 +602,7 @@ TEST_F(UmChromeOSPolicyTest, SetUpdateCheckAllowed(false); - const Time curr_time = fake_clock_.GetWallclockTime(); + const Time curr_time = fake_clock_->GetWallclockTime(); UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10)); update_state.download_errors_max = 1; update_state.download_errors.emplace_back( @@ -615,7 +635,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBackoffSatisfied) { SetUpdateCheckAllowed(false); - const Time curr_time = fake_clock_.GetWallclockTime(); + const Time curr_time = fake_clock_->GetWallclockTime(); UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10)); update_state.download_errors_max = 1; update_state.download_errors.emplace_back( @@ -649,7 +669,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBackoffDisabled) { SetUpdateCheckAllowed(false); - const Time curr_time = fake_clock_.GetWallclockTime(); + const Time curr_time = fake_clock_->GetWallclockTime(); UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10)); update_state.download_errors_max = 1; update_state.download_errors.emplace_back( @@ -682,7 +702,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffInteractive) { SetUpdateCheckAllowed(false); - const Time curr_time = fake_clock_.GetWallclockTime(); + const Time curr_time = fake_clock_->GetWallclockTime(); UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10)); update_state.download_errors_max = 1; update_state.download_errors.emplace_back( @@ -715,7 +735,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffDelta) { SetUpdateCheckAllowed(false); - const Time curr_time = fake_clock_.GetWallclockTime(); + const Time curr_time = fake_clock_->GetWallclockTime(); UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10)); update_state.download_errors_max = 1; update_state.download_errors.emplace_back( @@ -748,7 +768,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffUnofficialBuild) { SetUpdateCheckAllowed(false); - const Time curr_time = fake_clock_.GetWallclockTime(); + const Time curr_time = fake_clock_->GetWallclockTime(); UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10)); update_state.download_errors_max = 1; update_state.download_errors.emplace_back( @@ -1124,7 +1144,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10)); update_state.p2p_num_attempts = 1; update_state.p2p_first_attempted = - fake_clock_.GetWallclockTime() - + fake_clock_->GetWallclockTime() - TimeDelta::FromSeconds(ChromeOSPolicy::kMaxP2PAttemptsPeriodInSeconds + 1); UpdateDownloadParams result; @@ -1198,7 +1218,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedMaxErrorsNotExceeded) { UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10)); update_state.num_checks = 5; update_state.download_urls.emplace_back("http://another/fake/url/"); - Time t = fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(12); + Time t = fake_clock_->GetWallclockTime() - TimeDelta::FromSeconds(12); for (int i = 0; i < 5; i++) { update_state.download_errors.emplace_back( 0, ErrorCode::kDownloadTransferError, t); @@ -1227,7 +1247,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithSecondUrlMaxExceeded) { UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10)); update_state.num_checks = 10; update_state.download_urls.emplace_back("http://another/fake/url/"); - Time t = fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(12); + Time t = fake_clock_->GetWallclockTime() - TimeDelta::FromSeconds(12); for (int i = 0; i < 11; i++) { update_state.download_errors.emplace_back( 0, ErrorCode::kDownloadTransferError, t); @@ -1259,7 +1279,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithSecondUrlHardError) { update_state.download_errors.emplace_back( 0, ErrorCode::kPayloadHashMismatchError, - fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1)); + fake_clock_->GetWallclockTime() - TimeDelta::FromSeconds(1)); // Check that the UpdateCanStart returns true. UpdateDownloadParams result; @@ -1288,7 +1308,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedUrlWrapsAround) { update_state.download_errors.emplace_back( 1, ErrorCode::kPayloadHashMismatchError, - fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1)); + fake_clock_->GetWallclockTime() - TimeDelta::FromSeconds(1)); // Check that the UpdateCanStart returns true. UpdateDownloadParams result; @@ -1371,7 +1391,7 @@ TEST_F(UmChromeOSPolicyTest, // Override specific device policy attributes. fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(nullptr); - fake_state_.device_policy_provider()->var_owner()->reset(nullptr); + fake_state_.device_policy_provider()->var_has_owner()->reset(new bool(false)); fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset( new bool(false)); @@ -1389,148 +1409,6 @@ TEST_F(UmChromeOSPolicyTest, EXPECT_FALSE(result.do_increment_failures); } -TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedEthernetDefault) { - // Ethernet is always allowed. - - fake_state_.shill_provider()->var_conn_type()->reset( - new ConnectionType(ConnectionType::kEthernet)); - - bool result; - ExpectPolicyStatus( - EvalStatus::kSucceeded, &Policy::UpdateDownloadAllowed, &result); - EXPECT_TRUE(result); -} - -TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedWifiDefault) { - // Wifi is allowed if not tethered. - - fake_state_.shill_provider()->var_conn_type()->reset( - new ConnectionType(ConnectionType::kWifi)); - - bool result; - ExpectPolicyStatus( - EvalStatus::kSucceeded, &Policy::UpdateDownloadAllowed, &result); - EXPECT_TRUE(result); -} - -TEST_F(UmChromeOSPolicyTest, - UpdateCurrentConnectionNotAllowedWifiTetheredDefault) { - // Tethered wifi is not allowed by default. - - fake_state_.shill_provider()->var_conn_type()->reset( - new ConnectionType(ConnectionType::kWifi)); - fake_state_.shill_provider()->var_conn_tethering()->reset( - new ConnectionTethering(ConnectionTethering::kConfirmed)); - - bool result; - ExpectPolicyStatus( - EvalStatus::kAskMeAgainLater, &Policy::UpdateDownloadAllowed, &result); -} - -TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedWifiTetheredPolicyOverride) { - // Tethered wifi can be allowed by policy. - - fake_state_.shill_provider()->var_conn_type()->reset( - new ConnectionType(ConnectionType::kWifi)); - fake_state_.shill_provider()->var_conn_tethering()->reset( - new ConnectionTethering(ConnectionTethering::kConfirmed)); - set<ConnectionType> allowed_connections; - allowed_connections.insert(ConnectionType::kCellular); - fake_state_.device_policy_provider() - ->var_allowed_connection_types_for_update() - ->reset(new set<ConnectionType>(allowed_connections)); - - bool result; - ExpectPolicyStatus( - EvalStatus::kSucceeded, &Policy::UpdateDownloadAllowed, &result); - EXPECT_TRUE(result); -} - -TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedWimaxDefault) { - // Wimax is always allowed. - - fake_state_.shill_provider()->var_conn_type()->reset( - new ConnectionType(ConnectionType::kWifi)); - - bool result; - ExpectPolicyStatus( - EvalStatus::kSucceeded, &Policy::UpdateDownloadAllowed, &result); - EXPECT_TRUE(result); -} - -TEST_F(UmChromeOSPolicyTest, - UpdateCurrentConnectionNotAllowedBluetoothDefault) { - // Bluetooth is never allowed. - - fake_state_.shill_provider()->var_conn_type()->reset( - new ConnectionType(ConnectionType::kBluetooth)); - - bool result; - ExpectPolicyStatus( - EvalStatus::kAskMeAgainLater, &Policy::UpdateDownloadAllowed, &result); -} - -TEST_F(UmChromeOSPolicyTest, - UpdateCurrentConnectionNotAllowedBluetoothPolicyCannotOverride) { - // Bluetooth cannot be allowed even by policy. - - fake_state_.shill_provider()->var_conn_type()->reset( - new ConnectionType(ConnectionType::kBluetooth)); - set<ConnectionType> allowed_connections; - allowed_connections.insert(ConnectionType::kBluetooth); - fake_state_.device_policy_provider() - ->var_allowed_connection_types_for_update() - ->reset(new set<ConnectionType>(allowed_connections)); - - bool result; - ExpectPolicyStatus( - EvalStatus::kAskMeAgainLater, &Policy::UpdateDownloadAllowed, &result); -} - -TEST_F(UmChromeOSPolicyTest, UpdateCurrentConnectionNotAllowedCellularDefault) { - // Cellular is not allowed by default. - - fake_state_.shill_provider()->var_conn_type()->reset( - new ConnectionType(ConnectionType::kCellular)); - - bool result; - ExpectPolicyStatus( - EvalStatus::kAskMeAgainLater, &Policy::UpdateDownloadAllowed, &result); -} - -TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedCellularPolicyOverride) { - // Update over cellular can be enabled by policy. - - fake_state_.shill_provider()->var_conn_type()->reset( - new ConnectionType(ConnectionType::kCellular)); - set<ConnectionType> allowed_connections; - allowed_connections.insert(ConnectionType::kCellular); - fake_state_.device_policy_provider() - ->var_allowed_connection_types_for_update() - ->reset(new set<ConnectionType>(allowed_connections)); - - bool result; - ExpectPolicyStatus( - EvalStatus::kSucceeded, &Policy::UpdateDownloadAllowed, &result); - EXPECT_TRUE(result); -} - -TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedCellularUserOverride) { - // Update over cellular can be enabled by user settings, but only if policy - // is present and does not determine allowed connections. - - fake_state_.shill_provider()->var_conn_type()->reset( - new ConnectionType(ConnectionType::kCellular)); - set<ConnectionType> allowed_connections; - allowed_connections.insert(ConnectionType::kCellular); - fake_state_.updater_provider()->var_cellular_enabled()->reset(new bool(true)); - - bool result; - ExpectPolicyStatus( - EvalStatus::kSucceeded, &Policy::UpdateDownloadAllowed, &result); - EXPECT_TRUE(result); -} - TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedScatteringSupressedDueToP2P) { // The UpdateCanStart policy returns true; scattering should have applied, but // P2P download is allowed. Scattering values are nonetheless returned, and so @@ -1565,7 +1443,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBackoffSupressedDueToP2P) { SetUpdateCheckAllowed(false); - const Time curr_time = fake_clock_.GetWallclockTime(); + const Time curr_time = fake_clock_->GetWallclockTime(); UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10)); update_state.download_errors_max = 1; update_state.download_errors.emplace_back( @@ -1616,7 +1494,7 @@ TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedByUpdater) { TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedDeviceEnterpriseEnrolled) { fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(nullptr); - fake_state_.device_policy_provider()->var_owner()->reset(nullptr); + fake_state_.device_policy_provider()->var_has_owner()->reset(new bool(false)); bool result; ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result); @@ -1631,7 +1509,7 @@ TEST_F(UmChromeOSPolicyTest, P2PEnabledChangedBlocks) { TEST_F(UmChromeOSPolicyTest, UpdateCanBeAppliedForcedUpdatesDisablesTimeRestrictions) { - Time curr_time = fake_clock_.GetWallclockTime(); + Time curr_time = fake_clock_->GetWallclockTime(); fake_state_.updater_provider()->var_forced_update_requested()->reset( new UpdateRequestStatus(UpdateRequestStatus::kInteractive)); // Should return kAskMeAgainLater when updated are not forced. @@ -1644,7 +1522,7 @@ TEST_F(UmChromeOSPolicyTest, } TEST_F(UmChromeOSPolicyTest, UpdateCanBeAppliedFailsInDisallowedTime) { - Time curr_time = fake_clock_.GetWallclockTime(); + Time curr_time = fake_clock_->GetWallclockTime(); TestDisallowedTimeIntervals( {WeeklyTimeInterval( WeeklyTime::FromTime(curr_time), @@ -1654,7 +1532,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCanBeAppliedFailsInDisallowedTime) { } TEST_F(UmChromeOSPolicyTest, UpdateCanBeAppliedOutsideDisallowedTime) { - Time curr_time = fake_clock_.GetWallclockTime(); + Time curr_time = fake_clock_->GetWallclockTime(); TestDisallowedTimeIntervals( {WeeklyTimeInterval( WeeklyTime::FromTime(curr_time - TimeDelta::FromHours(3)), @@ -1664,7 +1542,7 @@ TEST_F(UmChromeOSPolicyTest, UpdateCanBeAppliedOutsideDisallowedTime) { } TEST_F(UmChromeOSPolicyTest, UpdateCanBeAppliedPassesOnNonKiosk) { - Time curr_time = fake_clock_.GetWallclockTime(); + Time curr_time = fake_clock_->GetWallclockTime(); TestDisallowedTimeIntervals( {WeeklyTimeInterval( WeeklyTime::FromTime(curr_time), diff --git a/update_manager/default_policy.cc b/update_manager/default_policy.cc index 81ab795d..0713e06a 100644 --- a/update_manager/default_policy.cc +++ b/update_manager/default_policy.cc @@ -14,10 +14,12 @@ // limitations under the License. // +#include "update_engine/common/system_state.h" #include "update_engine/update_manager/default_policy.h" using chromeos_update_engine::ErrorCode; using chromeos_update_engine::InstallPlan; +using chromeos_update_engine::SystemState; namespace { @@ -31,19 +33,19 @@ const int kCheckIntervalInSeconds = 15 * 60; namespace chromeos_update_manager { -DefaultPolicy::DefaultPolicy(chromeos_update_engine::ClockInterface* clock) - : clock_(clock), aux_state_(new DefaultPolicyState()) {} - EvalStatus DefaultPolicy::UpdateCheckAllowed(EvaluationContext* ec, State* state, std::string* error, UpdateCheckParams* result) const { result->updates_enabled = true; result->target_channel.clear(); + result->lts_tag.clear(); result->target_version_prefix.clear(); result->rollback_allowed = false; result->rollback_allowed_milestones = -1; // No version rolls should happen. + result->rollback_on_channel_downgrade = false; result->interactive = false; + result->quick_fix_build_token.clear(); // Ensure that the minimum interval is set. If there's no clock, this defaults // to always allowing the update. @@ -51,8 +53,8 @@ EvalStatus DefaultPolicy::UpdateCheckAllowed(EvaluationContext* ec, ec->IsMonotonicTimeGreaterThan( aux_state_->last_check_allowed_time() + base::TimeDelta::FromSeconds(kCheckIntervalInSeconds))) { - if (clock_) - aux_state_->set_last_check_allowed_time(clock_->GetMonotonicTime()); + aux_state_->set_last_check_allowed_time( + SystemState::Get()->clock()->GetMonotonicTime()); return EvalStatus::kSucceeded; } @@ -87,14 +89,6 @@ EvalStatus DefaultPolicy::UpdateCanStart(EvaluationContext* ec, return EvalStatus::kSucceeded; } -EvalStatus DefaultPolicy::UpdateDownloadAllowed(EvaluationContext* ec, - State* state, - std::string* error, - bool* result) const { - *result = true; - return EvalStatus::kSucceeded; -} - EvalStatus DefaultPolicy::P2PEnabled(EvaluationContext* ec, State* state, std::string* error, diff --git a/update_manager/default_policy.h b/update_manager/default_policy.h index 1b284f41..c93bb46c 100644 --- a/update_manager/default_policy.h +++ b/update_manager/default_policy.h @@ -22,7 +22,6 @@ #include <base/time/time.h> -#include "update_engine/common/clock_interface.h" #include "update_engine/update_manager/policy.h" namespace chromeos_update_manager { @@ -60,9 +59,8 @@ class DefaultPolicyState { // actual policy being used by the UpdateManager. class DefaultPolicy : public Policy { public: - explicit DefaultPolicy(chromeos_update_engine::ClockInterface* clock); - DefaultPolicy() : DefaultPolicy(nullptr) {} - ~DefaultPolicy() override {} + DefaultPolicy() : aux_state_(new DefaultPolicyState()) {} + ~DefaultPolicy() override = default; // Policy overrides. EvalStatus UpdateCheckAllowed(EvaluationContext* ec, @@ -83,11 +81,6 @@ class DefaultPolicy : public Policy { UpdateDownloadParams* result, UpdateState update_state) const override; - EvalStatus UpdateDownloadAllowed(EvaluationContext* ec, - State* state, - std::string* error, - bool* result) const override; - EvalStatus P2PEnabled(EvaluationContext* ec, State* state, std::string* error, @@ -104,9 +97,6 @@ class DefaultPolicy : public Policy { std::string PolicyName() const override { return "DefaultPolicy"; } private: - // A clock interface. - chromeos_update_engine::ClockInterface* clock_; - // An auxiliary state object. std::unique_ptr<DefaultPolicyState> aux_state_; diff --git a/update_manager/device_policy_provider.h b/update_manager/device_policy_provider.h index 873282ef..5112f684 100644 --- a/update_manager/device_policy_provider.h +++ b/update_manager/device_policy_provider.h @@ -21,6 +21,7 @@ #include <string> #include <base/time/time.h> +#include <base/version.h> #include <policy/libpolicy.h> #include "update_engine/update_manager/provider.h" @@ -44,6 +45,8 @@ class DevicePolicyProvider : public Provider { virtual Variable<bool>* var_release_channel_delegated() = 0; + virtual Variable<std::string>* var_release_lts_tag() = 0; + virtual Variable<bool>* var_update_disabled() = 0; virtual Variable<std::string>* var_target_version_prefix() = 0; @@ -66,9 +69,9 @@ class DevicePolicyProvider : public Provider { virtual Variable<std::set<chromeos_update_engine::ConnectionType>>* var_allowed_connection_types_for_update() = 0; - // Variable stating the name of the device owner. For enterprise enrolled - // devices, this will be an empty string. - virtual Variable<std::string>* var_owner() = 0; + // Variable stating whether the device has an owner. For enterprise enrolled + // devices, this will be false as the device owner has an empty string. + virtual Variable<bool>* var_has_owner() = 0; virtual Variable<bool>* var_http_downloads_enabled() = 0; @@ -85,6 +88,19 @@ class DevicePolicyProvider : public Provider { virtual Variable<WeeklyTimeIntervalVector>* var_disallowed_time_intervals() = 0; + // Variable that determins whether we should powerwash and rollback on channel + // downgrade for enrolled devices. + virtual Variable<ChannelDowngradeBehavior>* + var_channel_downgrade_behavior() = 0; + + // Variable that contains Chrome OS minimum required version. It contains a + // Chrome OS version number. + virtual Variable<base::Version>* var_device_minimum_version() = 0; + + // Variable that contains a token which maps to a Chrome OS Quick Fix Build to + // which the device would be updated if not blocked by another policy. + virtual Variable<std::string>* var_quick_fix_build_token() = 0; + protected: DevicePolicyProvider() {} diff --git a/update_manager/enterprise_device_policy_impl.cc b/update_manager/enterprise_device_policy_impl.cc index a3430ef2..b9a11e11 100644 --- a/update_manager/enterprise_device_policy_impl.cc +++ b/update_manager/enterprise_device_policy_impl.cc @@ -62,13 +62,37 @@ EvalStatus EnterpriseDevicePolicyImpl::UpdateCheckAllowed( ec->GetValue(system_provider->var_kiosk_required_platform_version()); if (!kiosk_required_platform_version_p) { LOG(INFO) << "Kiosk app required platform version is not fetched, " - "blocking update checks"; + "blocking update checks."; return EvalStatus::kAskMeAgainLater; + } else if (kiosk_required_platform_version_p->empty()) { + // The platform version could not be fetched several times. Update + // based on |DeviceMinimumVersion| instead (crbug.com/1048931). + const base::Version* device_minimum_version_p = + ec->GetValue(dp_provider->var_device_minimum_version()); + const base::Version* current_version_p( + ec->GetValue(system_provider->var_chromeos_version())); + if (device_minimum_version_p && device_minimum_version_p->IsValid() && + current_version_p && current_version_p->IsValid() && + *current_version_p > *device_minimum_version_p) { + // Do not update if the current version is newer than the minimum + // version. + LOG(INFO) << "Reading kiosk app required platform version failed " + "repeatedly but current version is newer than " + "DeviceMinimumVersion. Blocking update checks. " + "Current version: " + << *current_version_p + << " DeviceMinimumVersion: " << *device_minimum_version_p; + return EvalStatus::kAskMeAgainLater; + } + LOG(WARNING) << "Reading kiosk app required platform version failed " + "repeatedly. Attempting an update without it now."; + // An empty string for |target_version_prefix| allows arbitrary updates. + result->target_version_prefix = ""; + } else { + result->target_version_prefix = *kiosk_required_platform_version_p; + LOG(INFO) << "Allow kiosk app to control Chrome version policy is set, " + << "target version is " << result->target_version_prefix; } - - result->target_version_prefix = *kiosk_required_platform_version_p; - LOG(INFO) << "Allow kiosk app to control Chrome version policy is set, " - << "target version is " << result->target_version_prefix; // TODO(hunyadym): Add support for allowing rollback using the manifest // (if policy doesn't specify otherwise). } else { @@ -91,22 +115,18 @@ EvalStatus EnterpriseDevicePolicyImpl::UpdateCheckAllowed( case RollbackToTargetVersion::kDisabled: LOG(INFO) << "Policy disables rollbacks."; result->rollback_allowed = false; + result->rollback_data_save_requested = false; break; case RollbackToTargetVersion::kRollbackAndPowerwash: LOG(INFO) << "Policy allows rollbacks with powerwash."; result->rollback_allowed = true; + result->rollback_data_save_requested = false; break; case RollbackToTargetVersion::kRollbackAndRestoreIfPossible: LOG(INFO) << "Policy allows rollbacks, also tries to restore if possible."; - // We don't support restore yet, but policy still allows rollback. result->rollback_allowed = true; - break; - case RollbackToTargetVersion::kRollbackOnlyIfRestorePossible: - LOG(INFO) << "Policy only allows rollbacks if restore is possible."; - // We don't support restore yet, policy doesn't allow rollback in this - // case. - result->rollback_allowed = false; + result->rollback_data_save_requested = true; break; case RollbackToTargetVersion::kMaxValue: NOTREACHED(); @@ -121,14 +141,35 @@ EvalStatus EnterpriseDevicePolicyImpl::UpdateCheckAllowed( if (rollback_allowed_milestones_p) result->rollback_allowed_milestones = *rollback_allowed_milestones_p; - // Determine whether a target channel is dictated by policy. + // Determine whether a target channel is dictated by policy and whether we + // should rollback in case that channel is more stable. const bool* release_channel_delegated_p = ec->GetValue(dp_provider->var_release_channel_delegated()); if (release_channel_delegated_p && !(*release_channel_delegated_p)) { const string* release_channel_p = ec->GetValue(dp_provider->var_release_channel()); - if (release_channel_p) + if (release_channel_p) { result->target_channel = *release_channel_p; + const ChannelDowngradeBehavior* channel_downgrade_behavior_p = + ec->GetValue(dp_provider->var_channel_downgrade_behavior()); + if (channel_downgrade_behavior_p && + *channel_downgrade_behavior_p == + ChannelDowngradeBehavior::kRollback) { + result->rollback_on_channel_downgrade = true; + } + } + } + + const string* release_lts_tag_p = + ec->GetValue(dp_provider->var_release_lts_tag()); + if (release_lts_tag_p) { + result->lts_tag = *release_lts_tag_p; + } + + const string* quick_fix_build_token_p = + ec->GetValue(dp_provider->var_quick_fix_build_token()); + if (quick_fix_build_token_p) { + result->quick_fix_build_token = *quick_fix_build_token_p; } } return EvalStatus::kContinue; diff --git a/update_manager/enterprise_device_policy_impl_unittest.cc b/update_manager/enterprise_device_policy_impl_unittest.cc new file mode 100644 index 00000000..30f54b1c --- /dev/null +++ b/update_manager/enterprise_device_policy_impl_unittest.cc @@ -0,0 +1,170 @@ +// +// 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/update_manager/enterprise_device_policy_impl.h" + +#include <memory> + +#include "update_engine/update_manager/policy_test_utils.h" + +namespace chromeos_update_manager { + +class UmEnterpriseDevicePolicyImplTest : public UmPolicyTestBase { + protected: + UmEnterpriseDevicePolicyImplTest() : UmPolicyTestBase() { + policy_ = std::make_unique<EnterpriseDevicePolicyImpl>(); + } + + void SetUpDefaultState() override { + UmPolicyTestBase::SetUpDefaultState(); + + fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset( + new bool(true)); + } +}; + +TEST_F(UmEnterpriseDevicePolicyImplTest, KioskAppVersionSet) { + fake_state_.device_policy_provider()->var_update_disabled()->reset( + new bool(true)); + fake_state_.device_policy_provider() + ->var_allow_kiosk_app_control_chrome_version() + ->reset(new bool(true)); + + fake_state_.system_provider()->var_kiosk_required_platform_version()->reset( + new std::string("1234.5.6")); + + UpdateCheckParams result; + ExpectPolicyStatus( + EvalStatus::kContinue, &Policy::UpdateCheckAllowed, &result); + EXPECT_EQ(result.target_version_prefix, "1234.5.6"); +} + +TEST_F(UmEnterpriseDevicePolicyImplTest, KioskAppVersionUnreadableNoUpdate) { + fake_state_.device_policy_provider()->var_update_disabled()->reset( + new bool(true)); + fake_state_.device_policy_provider() + ->var_allow_kiosk_app_control_chrome_version() + ->reset(new bool(true)); + + fake_state_.system_provider()->var_kiosk_required_platform_version()->reset( + nullptr); + + UpdateCheckParams result; + ExpectPolicyStatus( + EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result); +} + +TEST_F(UmEnterpriseDevicePolicyImplTest, KioskAppVersionUnreadableUpdate) { + fake_state_.device_policy_provider()->var_update_disabled()->reset( + new bool(true)); + fake_state_.device_policy_provider() + ->var_allow_kiosk_app_control_chrome_version() + ->reset(new bool(true)); + + // The real variable returns an empty string after several unsuccessful + // reading attempts. Fake this by setting it directly to empty string. + fake_state_.system_provider()->var_kiosk_required_platform_version()->reset( + new std::string("")); + + UpdateCheckParams result; + ExpectPolicyStatus( + EvalStatus::kContinue, &Policy::UpdateCheckAllowed, &result); + EXPECT_EQ(result.target_version_prefix, ""); +} + +TEST_F(UmEnterpriseDevicePolicyImplTest, + KioskAppVersionUnreadableUpdateWithMinVersion) { + fake_state_.device_policy_provider()->var_update_disabled()->reset( + new bool(true)); + fake_state_.device_policy_provider() + ->var_allow_kiosk_app_control_chrome_version() + ->reset(new bool(true)); + + // The real variable returns an empty string after several unsuccessful + // reading attempts. Fake this by setting it directly to empty string. + fake_state_.system_provider()->var_kiosk_required_platform_version()->reset( + new std::string("")); + // Update if the minimum version is above the current OS version. + fake_state_.device_policy_provider()->var_device_minimum_version()->reset( + new base::Version("2.0.0")); + fake_state_.system_provider()->var_chromeos_version()->reset( + new base::Version("1.0.0")); + + UpdateCheckParams result; + ExpectPolicyStatus( + EvalStatus::kContinue, &Policy::UpdateCheckAllowed, &result); + EXPECT_EQ(result.target_version_prefix, ""); +} + +TEST_F(UmEnterpriseDevicePolicyImplTest, + KioskAppVersionUnreadableNoUpdateWithMinVersion) { + fake_state_.device_policy_provider()->var_update_disabled()->reset( + new bool(true)); + fake_state_.device_policy_provider() + ->var_allow_kiosk_app_control_chrome_version() + ->reset(new bool(true)); + + // The real variable returns an empty string after several unsuccessful + // reading attempts. Fake this by setting it directly to empty string. + fake_state_.system_provider()->var_kiosk_required_platform_version()->reset( + new std::string("")); + // Block update if the minimum version is below the current OS version. + fake_state_.device_policy_provider()->var_device_minimum_version()->reset( + new base::Version("1.0.0")); + fake_state_.system_provider()->var_chromeos_version()->reset( + new base::Version("2.0.0")); + + UpdateCheckParams result; + ExpectPolicyStatus( + EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result); +} + +TEST_F(UmEnterpriseDevicePolicyImplTest, ChannelDowngradeBehaviorNoRollback) { + fake_state_.device_policy_provider()->var_release_channel_delegated()->reset( + new bool(false)); + fake_state_.device_policy_provider()->var_release_channel()->reset( + new std::string("stable-channel")); + + UpdateCheckParams result; + ExpectPolicyStatus( + EvalStatus::kContinue, &Policy::UpdateCheckAllowed, &result); + EXPECT_FALSE(result.rollback_on_channel_downgrade); +} + +TEST_F(UmEnterpriseDevicePolicyImplTest, ChannelDowngradeBehaviorRollback) { + fake_state_.device_policy_provider()->var_release_channel_delegated()->reset( + new bool(false)); + fake_state_.device_policy_provider()->var_release_channel()->reset( + new std::string("stable-channel")); + fake_state_.device_policy_provider()->var_channel_downgrade_behavior()->reset( + new ChannelDowngradeBehavior(ChannelDowngradeBehavior::kRollback)); + + UpdateCheckParams result; + ExpectPolicyStatus( + EvalStatus::kContinue, &Policy::UpdateCheckAllowed, &result); + EXPECT_TRUE(result.rollback_on_channel_downgrade); +} + +TEST_F(UmEnterpriseDevicePolicyImplTest, QuickFixBuildToken) { + fake_state_.device_policy_provider()->var_quick_fix_build_token()->reset( + new std::string("token")); + UpdateCheckParams result; + ExpectPolicyStatus( + EvalStatus::kContinue, &Policy::UpdateCheckAllowed, &result); + EXPECT_EQ(result.quick_fix_build_token, "token"); +} + +} // namespace chromeos_update_manager diff --git a/update_manager/enterprise_rollback_policy_impl.cc b/update_manager/enterprise_rollback_policy_impl.cc new file mode 100644 index 00000000..ab4e38cd --- /dev/null +++ b/update_manager/enterprise_rollback_policy_impl.cc @@ -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. +// + +#include "update_engine/update_manager/enterprise_rollback_policy_impl.h" + +using chromeos_update_engine::ErrorCode; +using chromeos_update_engine::InstallPlan; +using std::string; + +namespace chromeos_update_manager { + +EvalStatus EnterpriseRollbackPolicyImpl::UpdateCanBeApplied( + EvaluationContext* ec, + State* state, + string* error, + ErrorCode* result, + InstallPlan* install_plan) const { + if (install_plan && install_plan->is_rollback) { + LOG(INFO) + << "Update is enterprise rollback, allowing update to be applied."; + *result = ErrorCode::kSuccess; + return EvalStatus::kSucceeded; + } + return EvalStatus::kContinue; +} + +} // namespace chromeos_update_manager diff --git a/update_manager/enterprise_rollback_policy_impl.h b/update_manager/enterprise_rollback_policy_impl.h new file mode 100644 index 00000000..bcaf95ec --- /dev/null +++ b/update_manager/enterprise_rollback_policy_impl.h @@ -0,0 +1,56 @@ +// +// 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_UPDATE_MANAGER_ENTERPRISE_ROLLBACK_POLICY_IMPL_H_ +#define UPDATE_ENGINE_UPDATE_MANAGER_ENTERPRISE_ROLLBACK_POLICY_IMPL_H_ + +#include <string> + +#include "update_engine/common/error_code.h" +#include "update_engine/payload_consumer/install_plan.h" +#include "update_engine/update_manager/policy_utils.h" + +namespace chromeos_update_manager { + +// If the update is an enterprise rollback, this should not block the update +// to be applied. +class EnterpriseRollbackPolicyImpl : public PolicyImplBase { + public: + EnterpriseRollbackPolicyImpl() = default; + ~EnterpriseRollbackPolicyImpl() override = default; + + // Policy overrides. + EvalStatus UpdateCanBeApplied( + EvaluationContext* ec, + State* state, + std::string* error, + chromeos_update_engine::ErrorCode* result, + chromeos_update_engine::InstallPlan* install_plan) const override; + + protected: + std::string PolicyName() const override { + return "EnterpriseRollbackPolicyImpl"; + } + + private: + EnterpriseRollbackPolicyImpl(const EnterpriseRollbackPolicyImpl&) = delete; + EnterpriseRollbackPolicyImpl& operator=(const EnterpriseRollbackPolicyImpl&) = + delete; +}; + +} // namespace chromeos_update_manager + +#endif // UPDATE_ENGINE_UPDATE_MANAGER_ENTERPRISE_ROLLBACK_POLICY_IMPL_H_ diff --git a/update_manager/enterprise_rollback_policy_impl_unittest.cc b/update_manager/enterprise_rollback_policy_impl_unittest.cc new file mode 100644 index 00000000..5cc5c75e --- /dev/null +++ b/update_manager/enterprise_rollback_policy_impl_unittest.cc @@ -0,0 +1,56 @@ +// +// 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 <memory> + +#include "update_engine/update_manager/enterprise_rollback_policy_impl.h" +#include "update_engine/update_manager/policy_test_utils.h" +#include "update_engine/update_manager/weekly_time.h" + +using chromeos_update_engine::ErrorCode; +using chromeos_update_engine::InstallPlan; + +namespace chromeos_update_manager { + +class UmEnterpriseRollbackPolicyImplTest : public UmPolicyTestBase { + protected: + UmEnterpriseRollbackPolicyImplTest() { + policy_ = std::make_unique<EnterpriseRollbackPolicyImpl>(); + } +}; + +TEST_F(UmEnterpriseRollbackPolicyImplTest, + ContinueWhenUpdateIsNotEnterpriseRollback) { + InstallPlan install_plan{.is_rollback = false}; + ErrorCode result; + ExpectPolicyStatus(EvalStatus::kContinue, + &Policy::UpdateCanBeApplied, + &result, + &install_plan); +} + +TEST_F(UmEnterpriseRollbackPolicyImplTest, + SuccessWhenUpdateIsEnterpriseRollback) { + InstallPlan install_plan{.is_rollback = true}; + ErrorCode result; + ExpectPolicyStatus(EvalStatus::kSucceeded, + &Policy::UpdateCanBeApplied, + &result, + &install_plan); + EXPECT_EQ(result, ErrorCode::kSuccess); +} + +} // namespace chromeos_update_manager diff --git a/update_manager/evaluation_context-inl.h b/update_manager/evaluation_context-inl.h index 59d85da0..82861fa2 100644 --- a/update_manager/evaluation_context-inl.h +++ b/update_manager/evaluation_context-inl.h @@ -39,7 +39,7 @@ const T* EvaluationContext::GetValue(Variable<T>* var) { std::string errmsg; const T* result = var->GetValue(RemainingTime(evaluation_monotonic_deadline_), &errmsg); - if (result == nullptr) { + if (result == nullptr && !var->IsMissingOk()) { LOG(WARNING) << "Error reading Variable " << var->GetName() << ": \"" << errmsg << "\""; } diff --git a/update_manager/evaluation_context.cc b/update_manager/evaluation_context.cc index e796fec2..b86f41cf 100644 --- a/update_manager/evaluation_context.cc +++ b/update_manager/evaluation_context.cc @@ -27,6 +27,7 @@ #include <base/strings/string_util.h> #include <base/values.h> +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" using base::Callback; @@ -34,7 +35,7 @@ using base::Closure; using base::Time; using base::TimeDelta; using brillo::MessageLoop; -using chromeos_update_engine::ClockInterface; +using chromeos_update_engine::SystemState; using std::string; using std::unique_ptr; @@ -65,12 +66,10 @@ TimeDelta GetTimeout(Time curr, Time expires) { namespace chromeos_update_manager { EvaluationContext::EvaluationContext( - ClockInterface* clock, TimeDelta evaluation_timeout, TimeDelta expiration_timeout, unique_ptr<Callback<void(EvaluationContext*)>> unregister_cb) - : clock_(clock), - evaluation_timeout_(evaluation_timeout), + : evaluation_timeout_(evaluation_timeout), expiration_timeout_(expiration_timeout), unregister_cb_(std::move(unregister_cb)), weak_ptr_factory_(this) { @@ -98,13 +97,15 @@ unique_ptr<Closure> EvaluationContext::RemoveObserversAndTimeout() { TimeDelta EvaluationContext::RemainingTime(Time monotonic_deadline) const { if (monotonic_deadline.is_max()) return TimeDelta::Max(); - TimeDelta remaining = monotonic_deadline - clock_->GetMonotonicTime(); + TimeDelta remaining = + monotonic_deadline - SystemState::Get()->clock()->GetMonotonicTime(); return std::max(remaining, TimeDelta()); } Time EvaluationContext::MonotonicDeadline(TimeDelta timeout) { - return (timeout.is_max() ? Time::Max() - : clock_->GetMonotonicTime() + timeout); + return (timeout.is_max() + ? Time::Max() + : SystemState::Get()->clock()->GetMonotonicTime() + timeout); } void EvaluationContext::ValueChanged(BaseVariable* var) { @@ -139,8 +140,9 @@ bool EvaluationContext::IsMonotonicTimeGreaterThan(Time timestamp) { } void EvaluationContext::ResetEvaluation() { - evaluation_start_wallclock_ = clock_->GetWallclockTime(); - evaluation_start_monotonic_ = clock_->GetMonotonicTime(); + const auto* clock = SystemState::Get()->clock(); + evaluation_start_wallclock_ = clock->GetWallclockTime(); + evaluation_start_monotonic_ = clock->GetMonotonicTime(); reevaluation_time_wallclock_ = Time::Max(); reevaluation_time_monotonic_ = Time::Max(); evaluation_monotonic_deadline_ = MonotonicDeadline(evaluation_timeout_); diff --git a/update_manager/evaluation_context.h b/update_manager/evaluation_context.h index c68c4308..3460f2af 100644 --- a/update_manager/evaluation_context.h +++ b/update_manager/evaluation_context.h @@ -23,12 +23,10 @@ #include <base/bind.h> #include <base/callback.h> -#include <base/memory/ref_counted.h> #include <base/memory/weak_ptr.h> #include <base/time/time.h> #include <brillo/message_loops/message_loop.h> -#include "update_engine/common/clock_interface.h" #include "update_engine/update_manager/boxed_value.h" #include "update_engine/update_manager/variable.h" @@ -46,7 +44,7 @@ namespace chromeos_update_manager { // // Example: // -// scoped_refptr<EvaluationContext> ec = new EvaluationContext(...); +// auto ec = std::make_shared<EvaluationContext>(...); // // ... // // The following call to ResetEvaluation() is optional. Use it to reset the @@ -62,18 +60,14 @@ namespace chromeos_update_manager { // // If the provided |closure| wants to re-evaluate the policy, it should // // call ec->ResetEvaluation() to start a new evaluation. // -class EvaluationContext : public base::RefCounted<EvaluationContext>, - private BaseVariable::ObserverInterface { +class EvaluationContext : private BaseVariable::ObserverInterface { public: EvaluationContext( - chromeos_update_engine::ClockInterface* clock, base::TimeDelta evaluation_timeout, base::TimeDelta expiration_timeout, std::unique_ptr<base::Callback<void(EvaluationContext*)>> unregister_cb); - EvaluationContext(chromeos_update_engine::ClockInterface* clock, - base::TimeDelta evaluation_timeout) + explicit EvaluationContext(base::TimeDelta evaluation_timeout) : EvaluationContext( - clock, evaluation_timeout, base::TimeDelta::Max(), std::unique_ptr<base::Callback<void(EvaluationContext*)>>()) {} @@ -174,9 +168,6 @@ class EvaluationContext : public base::RefCounted<EvaluationContext>, // Whether the evaluation context has indeed expired. bool is_expired_ = false; - // Pointer to the mockable clock interface; - chromeos_update_engine::ClockInterface* const clock_; - // The timestamps when the evaluation of this EvaluationContext started, // corresponding to ClockInterface::GetWallclockTime() and // ClockInterface::GetMonotonicTime(), respectively. These values are reset diff --git a/update_manager/evaluation_context_unittest.cc b/update_manager/evaluation_context_unittest.cc index eb42eb74..fdb408b8 100644 --- a/update_manager/evaluation_context_unittest.cc +++ b/update_manager/evaluation_context_unittest.cc @@ -26,6 +26,7 @@ #include <gtest/gtest.h> #include "update_engine/common/fake_clock.h" +#include "update_engine/cros/fake_system_state.h" #include "update_engine/update_manager/fake_variable.h" #include "update_engine/update_manager/generic_variables.h" #include "update_engine/update_manager/mock_variable.h" @@ -39,6 +40,8 @@ using brillo::MessageLoop; using brillo::MessageLoopRunMaxIterations; using brillo::MessageLoopRunUntil; using chromeos_update_engine::FakeClock; +using chromeos_update_engine::FakeSystemState; +using std::shared_ptr; using std::string; using std::unique_ptr; using testing::_; @@ -59,14 +62,14 @@ bool GetBoolean(bool* value) { } template <typename T> -void ReadVar(scoped_refptr<EvaluationContext> ec, Variable<T>* var) { +void ReadVar(shared_ptr<EvaluationContext> ec, Variable<T>* var) { ec->GetValue(var); } // Runs |evaluation|; if the value pointed by |count_p| is greater than zero, // decrement it and schedule a reevaluation; otherwise, writes true to |done_p|. void EvaluateRepeatedly(Closure evaluation, - scoped_refptr<EvaluationContext> ec, + shared_ptr<EvaluationContext> ec, int* count_p, bool* done_p) { evaluation.Run(); @@ -87,16 +90,17 @@ void EvaluateRepeatedly(Closure evaluation, class UmEvaluationContextTest : public ::testing::Test { protected: void SetUp() override { + FakeSystemState::CreateInstance(); + fake_clock_ = FakeSystemState::Get()->fake_clock(); loop_.SetAsCurrent(); // Apr 22, 2009 19:25:00 UTC (this is a random reference point). - fake_clock_.SetMonotonicTime(Time::FromTimeT(1240428300)); + fake_clock_->SetMonotonicTime(Time::FromTimeT(1240428300)); // Mar 2, 2006 1:23:45 UTC. - fake_clock_.SetWallclockTime(Time::FromTimeT(1141262625)); - eval_ctx_ = new EvaluationContext( - &fake_clock_, + fake_clock_->SetWallclockTime(Time::FromTimeT(1141262625)); + eval_ctx_.reset(new EvaluationContext( default_timeout_, default_timeout_, - unique_ptr<base::Callback<void(EvaluationContext*)>>(nullptr)); + unique_ptr<base::Callback<void(EvaluationContext*)>>(nullptr))); } void TearDown() override { @@ -125,8 +129,8 @@ class UmEvaluationContextTest : public ::testing::Test { TimeDelta default_timeout_ = TimeDelta::FromSeconds(5); brillo::FakeMessageLoop loop_{nullptr}; - FakeClock fake_clock_; - scoped_refptr<EvaluationContext> eval_ctx_; + FakeClock* fake_clock_; + shared_ptr<EvaluationContext> eval_ctx_; // FakeVariables used for testing the EvaluationContext. These are required // here to prevent them from going away *before* the EvaluationContext under @@ -210,13 +214,7 @@ TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutWithoutVariables) { fake_const_var_.reset(new string("Hello world!")); EXPECT_EQ(*eval_ctx_->GetValue(&fake_const_var_), "Hello world!"); - EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout( -#if BASE_VER < 576279 - Bind(&base::DoNothing) -#else - base::DoNothing() -#endif - )); + EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(base::DoNothing())); } // Test that reevaluation occurs when an async variable it depends on changes. @@ -286,23 +284,11 @@ TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutExpires) { EXPECT_TRUE(value); // Ensure that we cannot reschedule an evaluation. - EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout( -#if BASE_VER < 576279 - Bind(&base::DoNothing) -#else - base::DoNothing() -#endif - )); + EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(base::DoNothing())); // Ensure that we can reschedule an evaluation after resetting expiration. eval_ctx_->ResetExpiration(); - EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout( -#if BASE_VER < 576279 - Bind(&base::DoNothing) -#else - base::DoNothing() -#endif - )); + EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(base::DoNothing())); } // Test that we clear the events when destroying the EvaluationContext. @@ -348,13 +334,7 @@ TEST_F(UmEvaluationContextTest, ObjectDeletedWithPendingEventsTest) { fake_poll_var_.reset(new string("Polled value")); eval_ctx_->GetValue(&fake_async_var_); eval_ctx_->GetValue(&fake_poll_var_); - EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout( -#if BASE_VER < 576279 - Bind(&base::DoNothing) -#else - base::DoNothing() -#endif - )); + EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(base::DoNothing())); // TearDown() checks for leaked observers on this async_variable, which means // that our object is still alive after removing its reference. } @@ -388,8 +368,8 @@ TEST_F(UmEvaluationContextTest, DefaultTimeout) { } TEST_F(UmEvaluationContextTest, TimeoutUpdatesWithMonotonicTime) { - fake_clock_.SetMonotonicTime(fake_clock_.GetMonotonicTime() + - TimeDelta::FromSeconds(1)); + fake_clock_->SetMonotonicTime(fake_clock_->GetMonotonicTime() + + TimeDelta::FromSeconds(1)); TimeDelta timeout = default_timeout_ - TimeDelta::FromSeconds(1); @@ -398,9 +378,9 @@ TEST_F(UmEvaluationContextTest, TimeoutUpdatesWithMonotonicTime) { } TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesWallclock) { - Time cur_time = fake_clock_.GetWallclockTime(); + Time cur_time = fake_clock_->GetWallclockTime(); // Advance the time on the clock but don't call ResetEvaluation yet. - fake_clock_.SetWallclockTime(cur_time + TimeDelta::FromSeconds(4)); + fake_clock_->SetWallclockTime(cur_time + TimeDelta::FromSeconds(4)); EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time - TimeDelta::FromSeconds(1))); @@ -410,7 +390,7 @@ TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesWallclock) { // Call ResetEvaluation now, which should use the new evaluation time. eval_ctx_->ResetEvaluation(); - cur_time = fake_clock_.GetWallclockTime(); + cur_time = fake_clock_->GetWallclockTime(); EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time - TimeDelta::FromSeconds(1))); EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time)); @@ -419,9 +399,9 @@ TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesWallclock) { } TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesMonotonic) { - Time cur_time = fake_clock_.GetMonotonicTime(); + Time cur_time = fake_clock_->GetMonotonicTime(); // Advance the time on the clock but don't call ResetEvaluation yet. - fake_clock_.SetMonotonicTime(cur_time + TimeDelta::FromSeconds(4)); + fake_clock_->SetMonotonicTime(cur_time + TimeDelta::FromSeconds(4)); EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time - TimeDelta::FromSeconds(1))); @@ -431,7 +411,7 @@ TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesMonotonic) { // Call ResetEvaluation now, which should use the new evaluation time. eval_ctx_->ResetEvaluation(); - cur_time = fake_clock_.GetMonotonicTime(); + cur_time = fake_clock_->GetMonotonicTime(); EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time - TimeDelta::FromSeconds(1))); EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time)); @@ -442,33 +422,21 @@ TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesMonotonic) { TEST_F(UmEvaluationContextTest, IsWallclockTimeGreaterThanSignalsTriggerReevaluation) { EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan( - fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(1))); + fake_clock_->GetWallclockTime() + TimeDelta::FromSeconds(1))); // The "false" from IsWallclockTimeGreaterThan means that's not that timestamp // yet, so this should schedule a callback for when that happens. - EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout( -#if BASE_VER < 576279 - Bind(&base::DoNothing) -#else - base::DoNothing() -#endif - )); + EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(base::DoNothing())); } TEST_F(UmEvaluationContextTest, IsMonotonicTimeGreaterThanSignalsTriggerReevaluation) { EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan( - fake_clock_.GetMonotonicTime() + TimeDelta::FromSeconds(1))); + fake_clock_->GetMonotonicTime() + TimeDelta::FromSeconds(1))); // The "false" from IsMonotonicTimeGreaterThan means that's not that timestamp // yet, so this should schedule a callback for when that happens. - EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout( -#if BASE_VER < 576279 - Bind(&base::DoNothing) -#else - base::DoNothing() -#endif - )); + EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(base::DoNothing())); } TEST_F(UmEvaluationContextTest, @@ -476,18 +444,12 @@ TEST_F(UmEvaluationContextTest, // IsWallclockTimeGreaterThan() should ignore timestamps on the past for // reevaluation. EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan( - fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(20))); + fake_clock_->GetWallclockTime() - TimeDelta::FromSeconds(20))); EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan( - fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1))); + fake_clock_->GetWallclockTime() - TimeDelta::FromSeconds(1))); // Callback should not be scheduled. - EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout( -#if BASE_VER < 576279 - Bind(&base::DoNothing) -#else - base::DoNothing() -#endif - )); + EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(base::DoNothing())); } TEST_F(UmEvaluationContextTest, @@ -495,18 +457,12 @@ TEST_F(UmEvaluationContextTest, // IsMonotonicTimeGreaterThan() should ignore timestamps on the past for // reevaluation. EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan( - fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(20))); + fake_clock_->GetMonotonicTime() - TimeDelta::FromSeconds(20))); EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan( - fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(1))); + fake_clock_->GetMonotonicTime() - TimeDelta::FromSeconds(1))); // Callback should not be scheduled. - EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout( -#if BASE_VER < 576279 - Bind(&base::DoNothing) -#else - base::DoNothing() -#endif - )); + EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(base::DoNothing())); } TEST_F(UmEvaluationContextTest, DumpContext) { diff --git a/update_manager/fake_device_policy_provider.h b/update_manager/fake_device_policy_provider.h index 7cd4d7b8..762bfc51 100644 --- a/update_manager/fake_device_policy_provider.h +++ b/update_manager/fake_device_policy_provider.h @@ -42,6 +42,10 @@ class FakeDevicePolicyProvider : public DevicePolicyProvider { return &var_release_channel_delegated_; } + FakeVariable<std::string>* var_release_lts_tag() override { + return &var_release_lts_tag_; + } + FakeVariable<bool>* var_update_disabled() override { return &var_update_disabled_; } @@ -68,7 +72,7 @@ class FakeDevicePolicyProvider : public DevicePolicyProvider { return &var_allowed_connection_types_for_update_; } - FakeVariable<std::string>* var_owner() override { return &var_owner_; } + FakeVariable<bool>* var_has_owner() override { return &var_has_owner_; } FakeVariable<bool>* var_http_downloads_enabled() override { return &var_http_downloads_enabled_; @@ -91,6 +95,19 @@ class FakeDevicePolicyProvider : public DevicePolicyProvider { return &var_disallowed_time_intervals_; } + FakeVariable<ChannelDowngradeBehavior>* var_channel_downgrade_behavior() + override { + return &var_channel_downgrade_behavior_; + } + + FakeVariable<base::Version>* var_device_minimum_version() override { + return &var_device_minimum_version_; + } + + FakeVariable<std::string>* var_quick_fix_build_token() override { + return &var_quick_fix_build_token_; + } + private: FakeVariable<bool> var_device_policy_is_loaded_{"policy_is_loaded", kVariableModePoll}; @@ -98,6 +115,8 @@ class FakeDevicePolicyProvider : public DevicePolicyProvider { kVariableModePoll}; FakeVariable<bool> var_release_channel_delegated_{"release_channel_delegated", kVariableModePoll}; + FakeVariable<std::string> var_release_lts_tag_{"release_lts_tag", + kVariableModePoll}; FakeVariable<bool> var_update_disabled_{"update_disabled", kVariableModePoll}; FakeVariable<std::string> var_target_version_prefix_{"target_version_prefix", kVariableModePoll}; @@ -110,7 +129,7 @@ class FakeDevicePolicyProvider : public DevicePolicyProvider { FakeVariable<std::set<chromeos_update_engine::ConnectionType>> var_allowed_connection_types_for_update_{ "allowed_connection_types_for_update", kVariableModePoll}; - FakeVariable<std::string> var_owner_{"owner", kVariableModePoll}; + FakeVariable<bool> var_has_owner_{"owner", kVariableModePoll}; FakeVariable<bool> var_http_downloads_enabled_{"http_downloads_enabled", kVariableModePoll}; FakeVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled", kVariableModePoll}; @@ -119,7 +138,13 @@ class FakeDevicePolicyProvider : public DevicePolicyProvider { FakeVariable<std::string> var_auto_launched_kiosk_app_id_{ "auto_launched_kiosk_app_id", kVariableModePoll}; FakeVariable<WeeklyTimeIntervalVector> var_disallowed_time_intervals_{ - "disallowed_time_intervals", kVariableModePoll}; + "disallowed_time_intervals", kVariableModeAsync}; + FakeVariable<ChannelDowngradeBehavior> var_channel_downgrade_behavior_{ + "channel_downgrade_behavior", kVariableModePoll}; + FakeVariable<base::Version> var_device_minimum_version_{ + "device_minimum_version", kVariableModePoll}; + FakeVariable<std::string> var_quick_fix_build_token_{"quick_fix_build_token", + kVariableModePoll}; DISALLOW_COPY_AND_ASSIGN(FakeDevicePolicyProvider); }; diff --git a/update_manager/fake_system_provider.h b/update_manager/fake_system_provider.h index f54951b3..b320c01a 100644 --- a/update_manager/fake_system_provider.h +++ b/update_manager/fake_system_provider.h @@ -50,6 +50,10 @@ class FakeSystemProvider : public SystemProvider { return &var_kiosk_required_platform_version_; } + FakeVariable<base::Version>* var_chromeos_version() override { + return &var_version_; + } + private: FakeVariable<bool> var_is_normal_boot_mode_{"is_normal_boot_mode", kVariableModeConst}; @@ -60,6 +64,8 @@ class FakeSystemProvider : public SystemProvider { FakeVariable<unsigned int> var_num_slots_{"num_slots", kVariableModePoll}; FakeVariable<std::string> var_kiosk_required_platform_version_{ "kiosk_required_platform_version", kVariableModePoll}; + FakeVariable<base::Version> var_version_{"chromeos_version", + kVariableModePoll}; DISALLOW_COPY_AND_ASSIGN(FakeSystemProvider); }; diff --git a/update_manager/fake_update_manager.h b/update_manager/fake_update_manager.h index 173b1a99..b8805828 100644 --- a/update_manager/fake_update_manager.h +++ b/update_manager/fake_update_manager.h @@ -26,13 +26,12 @@ namespace chromeos_update_manager { class FakeUpdateManager : public UpdateManager { public: - explicit FakeUpdateManager(chromeos_update_engine::ClockInterface* clock) - : UpdateManager(clock, - base::TimeDelta::FromSeconds(5), + FakeUpdateManager() + : UpdateManager(base::TimeDelta::FromSeconds(5), base::TimeDelta::FromHours(1), new FakeState()) { // The FakeUpdateManager uses a DefaultPolicy. - set_policy(new DefaultPolicy(clock)); + set_policy(new DefaultPolicy()); } // UpdateManager overrides. diff --git a/update_manager/fake_updater_provider.h b/update_manager/fake_updater_provider.h index 7295765d..d967f420 100644 --- a/update_manager/fake_updater_provider.h +++ b/update_manager/fake_updater_provider.h @@ -83,6 +83,10 @@ class FakeUpdaterProvider : public UpdaterProvider { return &var_update_restrictions_; } + FakeVariable<int64_t>* var_test_update_check_interval_timeout() override { + return &var_test_update_check_interval_timeout_; + } + private: FakeVariable<base::Time> var_updater_started_time_{"updater_started_time", kVariableModePoll}; @@ -108,6 +112,8 @@ class FakeUpdaterProvider : public UpdaterProvider { "forced_update_requested", kVariableModeAsync}; FakeVariable<UpdateRestrictions> var_update_restrictions_{ "update_restrictions", kVariableModePoll}; + FakeVariable<int64_t> var_test_update_check_interval_timeout_{ + "test_update_check_interval_timeout", kVariableModePoll}; DISALLOW_COPY_AND_ASSIGN(FakeUpdaterProvider); }; diff --git a/update_manager/minimum_version_policy_impl.cc b/update_manager/minimum_version_policy_impl.cc new file mode 100644 index 00000000..fb94ee49 --- /dev/null +++ b/update_manager/minimum_version_policy_impl.cc @@ -0,0 +1,56 @@ +// +// 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/update_manager/minimum_version_policy_impl.h" + +#include <base/version.h> + +using chromeos_update_engine::ErrorCode; +using chromeos_update_engine::InstallPlan; + +namespace chromeos_update_manager { + +EvalStatus MinimumVersionPolicyImpl::UpdateCanBeApplied( + EvaluationContext* ec, + State* state, + std::string* error, + ErrorCode* result, + InstallPlan* install_plan) const { + const base::Version* current_version( + ec->GetValue(state->system_provider()->var_chromeos_version())); + if (current_version == nullptr || !current_version->IsValid()) { + LOG(WARNING) << "Unable to access current version"; + return EvalStatus::kContinue; + } + + const base::Version* minimum_version = ec->GetValue( + state->device_policy_provider()->var_device_minimum_version()); + if (minimum_version == nullptr || !minimum_version->IsValid()) { + LOG(WARNING) << "Unable to access minimum version"; + return EvalStatus::kContinue; + } + + if (*current_version < *minimum_version) { + LOG(INFO) << "Updating from version less than minimum required" + ", allowing update to be applied."; + *result = ErrorCode::kSuccess; + return EvalStatus::kSucceeded; + } + + return EvalStatus::kContinue; +} + +} // namespace chromeos_update_manager diff --git a/update_manager/minimum_version_policy_impl.h b/update_manager/minimum_version_policy_impl.h new file mode 100644 index 00000000..600d6244 --- /dev/null +++ b/update_manager/minimum_version_policy_impl.h @@ -0,0 +1,54 @@ +// +// 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_UPDATE_MANAGER_MINIMUM_VERSION_POLICY_IMPL_H_ +#define UPDATE_ENGINE_UPDATE_MANAGER_MINIMUM_VERSION_POLICY_IMPL_H_ + +#include <string> + +#include "update_engine/common/error_code.h" +#include "update_engine/payload_consumer/install_plan.h" +#include "update_engine/update_manager/policy_utils.h" + +namespace chromeos_update_manager { + +// Check to see if an update happens from a version less than the minimum +// required one. +class MinimumVersionPolicyImpl : public PolicyImplBase { + public: + MinimumVersionPolicyImpl() = default; + ~MinimumVersionPolicyImpl() override = default; + + // If current version is less than the minimum required one, then this should + // not block the update to be applied. + EvalStatus UpdateCanBeApplied( + EvaluationContext* ec, + State* state, + std::string* error, + chromeos_update_engine::ErrorCode* result, + chromeos_update_engine::InstallPlan* install_plan) const override; + + protected: + std::string PolicyName() const override { return "MinimumVersionPolicyImpl"; } + + private: + MinimumVersionPolicyImpl(const MinimumVersionPolicyImpl&) = delete; + MinimumVersionPolicyImpl& operator=(const MinimumVersionPolicyImpl&) = delete; +}; + +} // namespace chromeos_update_manager + +#endif // UPDATE_ENGINE_UPDATE_MANAGER_MINIMUM_VERSION_POLICY_IMPL_H_ diff --git a/update_manager/minimum_version_policy_impl_unittest.cc b/update_manager/minimum_version_policy_impl_unittest.cc new file mode 100644 index 00000000..8e4dba55 --- /dev/null +++ b/update_manager/minimum_version_policy_impl_unittest.cc @@ -0,0 +1,111 @@ +// +// 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 <memory> + +#include "update_engine/update_manager/minimum_version_policy_impl.h" +#include "update_engine/update_manager/policy_test_utils.h" + +using chromeos_update_engine::ErrorCode; +using chromeos_update_engine::InstallPlan; + +namespace { + +const char* kInvalidVersion = "13315.woops.12"; +const char* kOldVersion = "13315.60.12"; +const char* kNewVersion = "13315.60.15"; + +} // namespace + +namespace chromeos_update_manager { + +class UmMinimumVersionPolicyImplTest : public UmPolicyTestBase { + protected: + UmMinimumVersionPolicyImplTest() { + policy_ = std::make_unique<MinimumVersionPolicyImpl>(); + } + + void SetCurrentVersion(const std::string& version) { + fake_state_.system_provider()->var_chromeos_version()->reset( + new base::Version(version)); + } + + void SetMinimumVersion(const std::string& version) { + fake_state_.device_policy_provider()->var_device_minimum_version()->reset( + new base::Version(version)); + } + + void TestPolicy(const EvalStatus& expected_status) { + InstallPlan install_plan; + ErrorCode result; + ExpectPolicyStatus( + expected_status, &Policy::UpdateCanBeApplied, &result, &install_plan); + if (expected_status == EvalStatus::kSucceeded) + EXPECT_EQ(result, ErrorCode::kSuccess); + } +}; + +TEST_F(UmMinimumVersionPolicyImplTest, ContinueWhenCurrentVersionIsNotSet) { + SetMinimumVersion(kNewVersion); + + TestPolicy(EvalStatus::kContinue); +} + +TEST_F(UmMinimumVersionPolicyImplTest, ContinueWhenCurrentVersionIsInvalid) { + SetCurrentVersion(kInvalidVersion); + SetMinimumVersion(kNewVersion); + + TestPolicy(EvalStatus::kContinue); +} + +TEST_F(UmMinimumVersionPolicyImplTest, ContinueWhenMinumumVersionIsNotSet) { + SetCurrentVersion(kOldVersion); + + TestPolicy(EvalStatus::kContinue); +} + +TEST_F(UmMinimumVersionPolicyImplTest, ContinueWhenMinumumVersionIsInvalid) { + SetCurrentVersion(kOldVersion); + SetMinimumVersion(kInvalidVersion); + + TestPolicy(EvalStatus::kContinue); +} + +TEST_F(UmMinimumVersionPolicyImplTest, + ContinueWhenCurrentVersionIsGreaterThanMinimumVersion) { + SetCurrentVersion(kNewVersion); + SetMinimumVersion(kOldVersion); + + TestPolicy(EvalStatus::kContinue); +} + +TEST_F(UmMinimumVersionPolicyImplTest, + ContinueWhenCurrentVersionIsEqualToMinimumVersion) { + SetCurrentVersion(kNewVersion); + SetMinimumVersion(kNewVersion); + + TestPolicy(EvalStatus::kContinue); +} + +TEST_F(UmMinimumVersionPolicyImplTest, + SuccessWhenCurrentVersionIsLessThanMinimumVersion) { + SetCurrentVersion(kOldVersion); + SetMinimumVersion(kNewVersion); + + TestPolicy(EvalStatus::kSucceeded); +} + +} // namespace chromeos_update_manager diff --git a/update_manager/mock_policy.h b/update_manager/mock_policy.h index 46b6c789..3c6313fd 100644 --- a/update_manager/mock_policy.h +++ b/update_manager/mock_policy.h @@ -29,8 +29,7 @@ namespace chromeos_update_manager { // A mocked implementation of Policy. class MockPolicy : public Policy { public: - explicit MockPolicy(chromeos_update_engine::ClockInterface* clock) - : default_policy_(clock) { + MockPolicy() { // We defer to the corresponding DefaultPolicy methods, by default. ON_CALL(*this, UpdateCheckAllowed(testing::_, testing::_, testing::_, testing::_)) @@ -46,11 +45,6 @@ class MockPolicy : public Policy { testing::_, testing::_, testing::_, testing::_, testing::_)) .WillByDefault( testing::Invoke(&default_policy_, &DefaultPolicy::UpdateCanStart)); - ON_CALL( - *this, - UpdateDownloadAllowed(testing::_, testing::_, testing::_, testing::_)) - .WillByDefault(testing::Invoke(&default_policy_, - &DefaultPolicy::UpdateDownloadAllowed)); ON_CALL(*this, P2PEnabled(testing::_, testing::_, testing::_, testing::_)) .WillByDefault( testing::Invoke(&default_policy_, &DefaultPolicy::P2PEnabled)); @@ -60,8 +54,6 @@ class MockPolicy : public Policy { .WillByDefault(testing::Invoke(&default_policy_, &DefaultPolicy::P2PEnabledChanged)); } - - MockPolicy() : MockPolicy(nullptr) {} ~MockPolicy() override {} // Policy overrides. diff --git a/update_manager/mock_update_manager.h b/update_manager/mock_update_manager.h new file mode 100644 index 00000000..06e17d81 --- /dev/null +++ b/update_manager/mock_update_manager.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_MOCK_UPDATE_MANAGER_H +#define UPDATE_ENGINE_MOCK_UPDATE_MANAGER_H + +#include <string> + +#include "update_engine/update_manager/update_manager.h" + +#include <gmock/gmock.h> + +namespace chromeos_update_manager { + +class MockUpdateManager : public UpdateManager { + public: + MockUpdateManager() + : UpdateManager(base::TimeDelta(), base::TimeDelta(), nullptr) {} + + MOCK_METHOD2( + AsyncPolicyRequestUpdateCheckAllowed, + void(base::Callback<void(EvalStatus, const UpdateCheckParams& result)> + callback, + EvalStatus (Policy::*policy_method)( + EvaluationContext*, State*, std::string*, UpdateCheckParams*) + const)); +}; + +} // namespace chromeos_update_manager + +#endif // UPDATE_ENGINE_MOCK_UPDATE_MANAGER_H diff --git a/update_manager/next_update_check_policy_impl.cc b/update_manager/next_update_check_policy_impl.cc index 6f9748e3..0a787186 100644 --- a/update_manager/next_update_check_policy_impl.cc +++ b/update_manager/next_update_check_policy_impl.cc @@ -72,6 +72,11 @@ EvalStatus NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime( ec->GetValue(updater_provider->var_updater_started_time()); POLICY_CHECK_VALUE_AND_FAIL(updater_started_time, error); + // This value is used for testing only and it will get deleted after the first + // time it is read. + const int64_t* interval_timeout = + ec->GetValue(updater_provider->var_test_update_check_interval_timeout()); + const Time* last_checked_time = ec->GetValue(updater_provider->var_last_checked_time()); @@ -83,13 +88,21 @@ EvalStatus NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime( // If this is the first attempt, compute and return an initial value. if (last_checked_time == nullptr || *last_checked_time < *updater_started_time) { - *next_update_check = *updater_started_time + - FuzzedInterval(&prng, - constants.timeout_initial_interval, - constants.timeout_regular_fuzz); + TimeDelta time_diff = + interval_timeout == nullptr + ? FuzzedInterval(&prng, + constants.timeout_initial_interval, + constants.timeout_regular_fuzz) + : TimeDelta::FromSeconds(*interval_timeout); + *next_update_check = *updater_started_time + time_diff; return EvalStatus::kSucceeded; } + if (interval_timeout != nullptr) { + *next_update_check = + *last_checked_time + TimeDelta::FromSeconds(*interval_timeout); + return EvalStatus::kSucceeded; + } // Check whether the server is enforcing a poll interval; if not, this value // will be zero. const unsigned int* server_dictated_poll_interval = diff --git a/update_manager/next_update_check_policy_impl_unittest.cc b/update_manager/next_update_check_policy_impl_unittest.cc index 58aff664..d80063de 100644 --- a/update_manager/next_update_check_policy_impl_unittest.cc +++ b/update_manager/next_update_check_policy_impl_unittest.cc @@ -52,14 +52,14 @@ TEST_F(UmNextUpdateCheckTimePolicyImplTest, // Set the last update time so it'll appear as if this is a first update check // in the lifetime of the current updater. fake_state_.updater_provider()->var_last_checked_time()->reset( - new Time(fake_clock_.GetWallclockTime() - TimeDelta::FromMinutes(10))); + new Time(fake_clock_->GetWallclockTime() - TimeDelta::FromMinutes(10))); CallMethodWithContext(&NextUpdateCheckTimePolicyImpl::NextUpdateCheckTime, &next_update_check, policy_test_constants); - EXPECT_LE(fake_clock_.GetWallclockTime(), next_update_check); - EXPECT_GE(fake_clock_.GetWallclockTime() + + EXPECT_LE(fake_clock_->GetWallclockTime(), next_update_check); + EXPECT_GE(fake_clock_->GetWallclockTime() + TimeDelta::FromSeconds( policy_test_constants.timeout_initial_interval + policy_test_constants.timeout_regular_fuzz / 2), @@ -75,12 +75,12 @@ TEST_F(UmNextUpdateCheckTimePolicyImplTest, RecurringCheckBaseIntervalAndFuzz) { &next_update_check, policy_test_constants); - EXPECT_LE(fake_clock_.GetWallclockTime() + + EXPECT_LE(fake_clock_->GetWallclockTime() + TimeDelta::FromSeconds( policy_test_constants.timeout_periodic_interval - policy_test_constants.timeout_regular_fuzz / 2), next_update_check); - EXPECT_GE(fake_clock_.GetWallclockTime() + + EXPECT_GE(fake_clock_->GetWallclockTime() + TimeDelta::FromSeconds( policy_test_constants.timeout_periodic_interval + policy_test_constants.timeout_regular_fuzz / 2), @@ -103,11 +103,11 @@ TEST_F(UmNextUpdateCheckTimePolicyImplTest, int expected_interval = policy_test_constants.timeout_periodic_interval * 4; EXPECT_LE( - fake_clock_.GetWallclockTime() + + fake_clock_->GetWallclockTime() + TimeDelta::FromSeconds(expected_interval - expected_interval / 2), next_update_check); EXPECT_GE( - fake_clock_.GetWallclockTime() + + fake_clock_->GetWallclockTime() + TimeDelta::FromSeconds(expected_interval + expected_interval / 2), next_update_check); } @@ -129,10 +129,10 @@ TEST_F(UmNextUpdateCheckTimePolicyImplTest, &next_update_check, policy_test_constants); - EXPECT_LE(fake_clock_.GetWallclockTime() + + EXPECT_LE(fake_clock_->GetWallclockTime() + TimeDelta::FromSeconds(kInterval - kInterval / 2), next_update_check); - EXPECT_GE(fake_clock_.GetWallclockTime() + + EXPECT_GE(fake_clock_->GetWallclockTime() + TimeDelta::FromSeconds(kInterval + kInterval / 2), next_update_check); } @@ -148,12 +148,12 @@ TEST_F(UmNextUpdateCheckTimePolicyImplTest, ExponentialBackoffIsCapped) { &next_update_check, policy_test_constants); - EXPECT_LE(fake_clock_.GetWallclockTime() + + EXPECT_LE(fake_clock_->GetWallclockTime() + TimeDelta::FromSeconds( policy_test_constants.timeout_max_backoff_interval - policy_test_constants.timeout_max_backoff_interval / 2), next_update_check); - EXPECT_GE(fake_clock_.GetWallclockTime() + + EXPECT_GE(fake_clock_->GetWallclockTime() + TimeDelta::FromSeconds( policy_test_constants.timeout_max_backoff_interval + policy_test_constants.timeout_max_backoff_interval / 2), diff --git a/update_manager/official_build_check_policy_impl.cc b/update_manager/official_build_check_policy_impl.cc index 096f7bf5..e80c09f8 100644 --- a/update_manager/official_build_check_policy_impl.cc +++ b/update_manager/official_build_check_policy_impl.cc @@ -27,8 +27,16 @@ EvalStatus OnlyUpdateOfficialBuildsPolicyImpl::UpdateCheckAllowed( const bool* is_official_build_p = ec->GetValue(state->system_provider()->var_is_official_build()); if (is_official_build_p != nullptr && !(*is_official_build_p)) { - LOG(INFO) << "Unofficial build, blocking periodic update checks."; - return EvalStatus::kAskMeAgainLater; + const int64_t* interval_timeout_p = ec->GetValue( + state->updater_provider()->var_test_update_check_interval_timeout()); + // The |interval_timeout | is used for testing only to test periodic + // update checks on unofficial images. + if (interval_timeout_p == nullptr) { + LOG(INFO) << "Unofficial build, blocking periodic update checks."; + return EvalStatus::kAskMeAgainLater; + } + LOG(INFO) << "Unofficial build, but periodic update check interval " + << "timeout is defined, so update is not blocked."; } return EvalStatus::kContinue; } diff --git a/update_manager/policy.h b/update_manager/policy.h index 5d65d9ae..fb4a1295 100644 --- a/update_manager/policy.h +++ b/update_manager/policy.h @@ -17,6 +17,7 @@ #ifndef UPDATE_ENGINE_UPDATE_MANAGER_POLICY_H_ #define UPDATE_ENGINE_UPDATE_MANAGER_POLICY_H_ +#include <memory> #include <string> #include <tuple> #include <vector> @@ -42,24 +43,35 @@ std::string ToString(EvalStatus status); // Parameters of an update check. These parameters are determined by the // UpdateCheckAllowed policy. struct UpdateCheckParams { - bool updates_enabled; // Whether the auto-updates are enabled on this build. + // Whether the auto-updates are enabled on this build. + bool updates_enabled{true}; // Attributes pertaining to the case where update checks are allowed. // // A target version prefix, if imposed by policy; otherwise, an empty string. std::string target_version_prefix; // Specifies whether rollback images are allowed by device policy. - bool rollback_allowed; + bool rollback_allowed{false}; + // Specifies if rollbacks should attempt to preserve some system state. + bool rollback_data_save_requested{false}; // Specifies the number of Chrome milestones rollback should be allowed, // starting from the stable version at any time. Value is -1 if unspecified // (e.g. no device policy is available yet), in this case no version // roll-forward should happen. - int rollback_allowed_milestones; + int rollback_allowed_milestones{0}; + // Whether a rollback with data save should be initiated on channel + // downgrade (e.g. beta to stable). + bool rollback_on_channel_downgrade{false}; // A target channel, if so imposed by policy; otherwise, an empty string. std::string target_channel; + // Specifies if the channel hint, e.g. LTS (Long Term Support) updates. + std::string lts_tag; + // Specifies a token which maps to a Chrome OS Quick Fix Build, if imposed by + // policy; otherwise, an empty string. + std::string quick_fix_build_token; // Whether the allowed update is interactive (user-initiated) or periodic. - bool interactive; + bool interactive{false}; }; // Input arguments to UpdateCanStart. @@ -218,9 +230,6 @@ class Policy { if (reinterpret_cast<typeof(&Policy::UpdateCanStart)>(policy_method) == &Policy::UpdateCanStart) return class_name + "UpdateCanStart"; - if (reinterpret_cast<typeof(&Policy::UpdateDownloadAllowed)>( - policy_method) == &Policy::UpdateDownloadAllowed) - return class_name + "UpdateDownloadAllowed"; if (reinterpret_cast<typeof(&Policy::P2PEnabled)>(policy_method) == &Policy::P2PEnabled) return class_name + "P2PEnabled"; @@ -269,17 +278,6 @@ class Policy { UpdateDownloadParams* result, UpdateState update_state) const = 0; - // Checks whether downloading of an update is allowed; currently, this checks - // whether the network connection type is suitable for updating over. May - // consult the shill provider as well as the device policy (if available). - // Returns |EvalStatus::kSucceeded|, setting |result| according to whether or - // not the current connection can be used; on error, returns - // |EvalStatus::kFailed| and sets |error| accordingly. - virtual EvalStatus UpdateDownloadAllowed(EvaluationContext* ec, - State* state, - std::string* error, - bool* result) const = 0; - // Checks whether P2P is enabled. This may consult device policy and other // global settings. virtual EvalStatus P2PEnabled(EvaluationContext* ec, @@ -308,6 +306,9 @@ class Policy { DISALLOW_COPY_AND_ASSIGN(Policy); }; +// Get system dependent policy implementation. +std::unique_ptr<Policy> GetSystemPolicy(); + } // namespace chromeos_update_manager #endif // UPDATE_ENGINE_UPDATE_MANAGER_POLICY_H_ diff --git a/update_manager/policy_test_utils.cc b/update_manager/policy_test_utils.cc index 5491e007..e8961b1b 100644 --- a/update_manager/policy_test_utils.cc +++ b/update_manager/policy_test_utils.cc @@ -20,11 +20,13 @@ #include <tuple> #include <vector> +#include "update_engine/cros/fake_system_state.h" #include "update_engine/update_manager/next_update_check_policy_impl.h" using base::Time; using base::TimeDelta; using chromeos_update_engine::ErrorCode; +using chromeos_update_engine::FakeSystemState; using std::string; using std::tuple; using std::vector; @@ -33,8 +35,10 @@ namespace chromeos_update_manager { void UmPolicyTestBase::SetUp() { loop_.SetAsCurrent(); + FakeSystemState::CreateInstance(); + fake_clock_ = FakeSystemState::Get()->fake_clock(); SetUpDefaultClock(); - eval_ctx_ = new EvaluationContext(&fake_clock_, TimeDelta::FromSeconds(5)); + eval_ctx_.reset(new EvaluationContext(TimeDelta::FromSeconds(5))); SetUpDefaultState(); } @@ -44,12 +48,12 @@ void UmPolicyTestBase::TearDown() { // Sets the clock to fixed values. void UmPolicyTestBase::SetUpDefaultClock() { - fake_clock_.SetMonotonicTime(Time::FromInternalValue(12345678L)); - fake_clock_.SetWallclockTime(Time::FromInternalValue(12345678901234L)); + fake_clock_->SetMonotonicTime(Time::FromInternalValue(12345678L)); + fake_clock_->SetWallclockTime(Time::FromInternalValue(12345678901234L)); } void UmPolicyTestBase::SetUpDefaultTimeProvider() { - Time current_time = fake_clock_.GetWallclockTime(); + Time current_time = FakeSystemState::Get()->clock()->GetWallclockTime(); base::Time::Exploded exploded; current_time.LocalExplode(&exploded); fake_state_.time_provider()->var_curr_hour()->reset(new int(exploded.hour)); @@ -61,9 +65,9 @@ void UmPolicyTestBase::SetUpDefaultTimeProvider() { void UmPolicyTestBase::SetUpDefaultState() { fake_state_.updater_provider()->var_updater_started_time()->reset( - new Time(fake_clock_.GetWallclockTime())); + new Time(fake_clock_->GetWallclockTime())); fake_state_.updater_provider()->var_last_checked_time()->reset( - new Time(fake_clock_.GetWallclockTime())); + new Time(fake_clock_->GetWallclockTime())); fake_state_.updater_provider()->var_consecutive_failed_update_checks()->reset( new unsigned int(0)); // NOLINT(readability/casting) fake_state_.updater_provider()->var_server_dictated_poll_interval()->reset( @@ -78,7 +82,8 @@ void UmPolicyTestBase::SetUpDefaultState() { // Returns a default UpdateState structure: UpdateState UmPolicyTestBase::GetDefaultUpdateState( TimeDelta first_seen_period) { - Time first_seen_time = fake_clock_.GetWallclockTime() - first_seen_period; + Time first_seen_time = + FakeSystemState::Get()->clock()->GetWallclockTime() - first_seen_period; UpdateState update_state = UpdateState(); // This is a non-interactive check returning a delta payload, seen for the diff --git a/update_manager/policy_test_utils.h b/update_manager/policy_test_utils.h index eb5758f0..72bd3bc6 100644 --- a/update_manager/policy_test_utils.h +++ b/update_manager/policy_test_utils.h @@ -91,9 +91,9 @@ class UmPolicyTestBase : public ::testing::Test { } brillo::FakeMessageLoop loop_{nullptr}; - chromeos_update_engine::FakeClock fake_clock_; + chromeos_update_engine::FakeClock* fake_clock_; FakeState fake_state_; - scoped_refptr<EvaluationContext> eval_ctx_; + std::shared_ptr<EvaluationContext> eval_ctx_; std::unique_ptr<Policy> policy_; }; diff --git a/update_manager/policy_utils.h b/update_manager/policy_utils.h index 32047803..aedb90cb 100644 --- a/update_manager/policy_utils.h +++ b/update_manager/policy_utils.h @@ -55,7 +55,6 @@ EvalStatus ConsultPolicies(const std::vector<Policy const*> policies, EvalStatus status = (policy->*policy_method)(ec, state, error, result, args...); if (status != EvalStatus::kContinue) { - LOG(INFO) << "decision by " << policy->PolicyRequestName(policy_method); return status; } } @@ -93,13 +92,6 @@ class PolicyImplBase : public Policy { return EvalStatus::kContinue; }; - EvalStatus UpdateDownloadAllowed(EvaluationContext* ec, - State* state, - std::string* error, - bool* result) const override { - return EvalStatus::kContinue; - }; - EvalStatus P2PEnabled(EvaluationContext* ec, State* state, std::string* error, diff --git a/update_manager/real_device_policy_provider.cc b/update_manager/real_device_policy_provider.cc index 586ee3e2..e7b964b0 100644 --- a/update_manager/real_device_policy_provider.cc +++ b/update_manager/real_device_policy_provider.cc @@ -25,8 +25,8 @@ #include <base/time/time.h> #include <policy/device_policy.h> +#include "update_engine/common/connection_utils.h" #include "update_engine/common/utils.h" -#include "update_engine/connection_utils.h" #include "update_engine/update_manager/generic_variables.h" using base::TimeDelta; @@ -105,10 +105,12 @@ void RealDevicePolicyProvider::RefreshDevicePolicyAndReschedule() { template <typename T> void RealDevicePolicyProvider::UpdateVariable( - AsyncCopyVariable<T>* var, bool (DevicePolicy::*getter_method)(T*) const) { + AsyncCopyVariable<T>* var, + // NOLINTNEXTLINE(readability/casting) + bool (DevicePolicy::*getter)(T*) const) { T new_value; if (policy_provider_->device_policy_is_loaded() && - (policy_provider_->GetDevicePolicy().*getter_method)(&new_value)) { + (policy_provider_->GetDevicePolicy().*getter)(&new_value)) { var->SetValue(new_value); } else { var->UnsetValue(); @@ -118,10 +120,10 @@ void RealDevicePolicyProvider::UpdateVariable( template <typename T> void RealDevicePolicyProvider::UpdateVariable( AsyncCopyVariable<T>* var, - bool (RealDevicePolicyProvider::*getter_method)(T*) const) { + bool (RealDevicePolicyProvider::*getter)(T*) const) { T new_value; if (policy_provider_->device_policy_is_loaded() && - (this->*getter_method)(&new_value)) { + (this->*getter)(&new_value)) { var->SetValue(new_value); } else { var->UnsetValue(); @@ -198,6 +200,30 @@ bool RealDevicePolicyProvider::ConvertDisallowedTimeIntervals( return true; } +bool RealDevicePolicyProvider::ConvertHasOwner(bool* has_owner) const { + string owner; + if (!policy_provider_->GetDevicePolicy().GetOwner(&owner)) { + return false; + } + *has_owner = !owner.empty(); + return true; +} + +bool RealDevicePolicyProvider::ConvertChannelDowngradeBehavior( + ChannelDowngradeBehavior* channel_downgrade_behavior) const { + int behavior; + if (!policy_provider_->GetDevicePolicy().GetChannelDowngradeBehavior( + &behavior)) { + return false; + } + if (behavior < static_cast<int>(ChannelDowngradeBehavior::kFirstValue) || + behavior > static_cast<int>(ChannelDowngradeBehavior::kLastValue)) { + return false; + } + *channel_downgrade_behavior = static_cast<ChannelDowngradeBehavior>(behavior); + return true; +} + void RealDevicePolicyProvider::RefreshDevicePolicy() { if (!policy_provider_->Reload()) { LOG(INFO) << "No device policies/settings present."; @@ -209,6 +235,7 @@ void RealDevicePolicyProvider::RefreshDevicePolicy() { UpdateVariable(&var_release_channel_, &DevicePolicy::GetReleaseChannel); UpdateVariable(&var_release_channel_delegated_, &DevicePolicy::GetReleaseChannelDelegated); + UpdateVariable(&var_release_lts_tag_, &DevicePolicy::GetReleaseLtsTag); UpdateVariable(&var_update_disabled_, &DevicePolicy::GetUpdateDisabled); UpdateVariable(&var_target_version_prefix_, &DevicePolicy::GetTargetVersionPrefix); @@ -225,7 +252,7 @@ void RealDevicePolicyProvider::RefreshDevicePolicy() { UpdateVariable( &var_allowed_connection_types_for_update_, &RealDevicePolicyProvider::ConvertAllowedConnectionTypesForUpdate); - UpdateVariable(&var_owner_, &DevicePolicy::GetOwner); + UpdateVariable(&var_has_owner_, &RealDevicePolicyProvider::ConvertHasOwner); UpdateVariable(&var_http_downloads_enabled_, &DevicePolicy::GetHttpDownloadsEnabled); UpdateVariable(&var_au_p2p_enabled_, &DevicePolicy::GetAuP2PEnabled); @@ -235,6 +262,12 @@ void RealDevicePolicyProvider::RefreshDevicePolicy() { &DevicePolicy::GetAutoLaunchedKioskAppId); UpdateVariable(&var_disallowed_time_intervals_, &RealDevicePolicyProvider::ConvertDisallowedTimeIntervals); + UpdateVariable(&var_channel_downgrade_behavior_, + &RealDevicePolicyProvider::ConvertChannelDowngradeBehavior); + UpdateVariable(&var_device_minimum_version_, + &DevicePolicy::GetHighestDeviceMinimumVersion); + UpdateVariable(&var_quick_fix_build_token_, + &DevicePolicy::GetDeviceQuickFixBuildToken); } } // namespace chromeos_update_manager diff --git a/update_manager/real_device_policy_provider.h b/update_manager/real_device_policy_provider.h index bda4cff1..0f84b770 100644 --- a/update_manager/real_device_policy_provider.h +++ b/update_manager/real_device_policy_provider.h @@ -34,7 +34,7 @@ namespace chromeos_update_manager { -// DevicePolicyProvider concrete implementation. +// |DevicePolicyProvider| concrete implementation. class RealDevicePolicyProvider : public DevicePolicyProvider { public: #if USE_DBUS @@ -64,6 +64,10 @@ class RealDevicePolicyProvider : public DevicePolicyProvider { return &var_release_channel_delegated_; } + Variable<std::string>* var_release_lts_tag() override { + return &var_release_lts_tag_; + } + Variable<bool>* var_update_disabled() override { return &var_update_disabled_; } @@ -89,7 +93,7 @@ class RealDevicePolicyProvider : public DevicePolicyProvider { return &var_allowed_connection_types_for_update_; } - Variable<std::string>* var_owner() override { return &var_owner_; } + Variable<bool>* var_has_owner() override { return &var_has_owner_; } Variable<bool>* var_http_downloads_enabled() override { return &var_http_downloads_enabled_; @@ -109,16 +113,30 @@ class RealDevicePolicyProvider : public DevicePolicyProvider { return &var_disallowed_time_intervals_; } + Variable<ChannelDowngradeBehavior>* var_channel_downgrade_behavior() + override { + return &var_channel_downgrade_behavior_; + } + + Variable<base::Version>* var_device_minimum_version() override { + return &var_device_minimum_version_; + } + + Variable<std::string>* var_quick_fix_build_token() override { + return &var_quick_fix_build_token_; + } + private: FRIEND_TEST(UmRealDevicePolicyProviderTest, RefreshScheduledTest); FRIEND_TEST(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded); FRIEND_TEST(UmRealDevicePolicyProviderTest, ValuesUpdated); + FRIEND_TEST(UmRealDevicePolicyProviderTest, HasOwnerConverted); - // A static handler for the PropertyChangedCompleted signal from the session + // A static handler for the |PropertyChangedCompleted| signal from the session // manager used as a callback. void OnPropertyChangedCompletedSignal(const std::string& success); - // Called when the signal in UpdateEngineLibcrosProxyResolvedInterface is + // Called when the signal in |UpdateEngineLibcrosProxyResolvedInterface| is // connected. void OnSignalConnected(const std::string& interface_name, const std::string& signal_name, @@ -134,36 +152,46 @@ class RealDevicePolicyProvider : public DevicePolicyProvider { // passed, which is a DevicePolicy getter method. template <typename T> void UpdateVariable(AsyncCopyVariable<T>* var, - bool (policy::DevicePolicy::*getter_method)(T*) const); + bool (policy::DevicePolicy::*getter)(T*) const); // Updates the async variable |var| based on the result value of the getter // method passed, which is a wrapper getter on this class. template <typename T> void UpdateVariable(AsyncCopyVariable<T>* var, - bool (RealDevicePolicyProvider::*getter_method)(T*) - const); + bool (RealDevicePolicyProvider::*getter)(T*) const); - // Wrapper for DevicePolicy::GetRollbackToTargetVersion() that converts the - // result to RollbackToTargetVersion. + // Wrapper for |DevicePolicy::GetRollbackToTargetVersion()| that converts the + // result to |RollbackToTargetVersion|. bool ConvertRollbackToTargetVersion( RollbackToTargetVersion* rollback_to_target_version) const; - // Wrapper for DevicePolicy::GetScatterFactorInSeconds() that converts the - // result to a base::TimeDelta. It returns the same value as - // GetScatterFactorInSeconds(). + // Wrapper for |DevicePolicy::GetScatterFactorInSeconds()| that converts the + // result to a |base::TimeDelta|. It returns the same value as + // |GetScatterFactorInSeconds()|. bool ConvertScatterFactor(base::TimeDelta* scatter_factor) const; - // Wrapper for DevicePolicy::GetAllowedConnectionTypesForUpdate() that - // converts the result to a set of ConnectionType elements instead of strings. + // Wrapper for |DevicePolicy::GetAllowedConnectionTypesForUpdate()| that + // converts the result to a set of |ConnectionType| elements instead of + // strings. bool ConvertAllowedConnectionTypesForUpdate( std::set<chromeos_update_engine::ConnectionType>* allowed_types) const; - // Wrapper for DevicePolicy::GetUpdateTimeRestrictions() that converts - // the DevicePolicy::WeeklyTimeInterval structs to WeeklyTimeInterval objects, - // which offer more functionality. + // Wrapper for |DevicePolicy::GetUpdateTimeRestrictions()| that converts + // the |DevicePolicy::WeeklyTimeInterval| structs to |WeeklyTimeInterval| + // objects, which offer more functionality. bool ConvertDisallowedTimeIntervals( WeeklyTimeIntervalVector* disallowed_intervals_out) const; + // Wrapper for |DevicePolicy::GetOwner()| that converts the result to a + // boolean of whether the device has an owner. (Enterprise enrolled + // devices do not have an owner). + bool ConvertHasOwner(bool* has_owner) const; + + // Wrapper for |DevicePolicy::GetChannelDowngradeBehavior| that converts the + // result to |ChannelDowngradeBehavior|. + bool ConvertChannelDowngradeBehavior( + ChannelDowngradeBehavior* channel_downgrade_behavior) const; + // Used for fetching information about the device policy. policy::PolicyProvider* policy_provider_; @@ -181,10 +209,11 @@ class RealDevicePolicyProvider : public DevicePolicyProvider { AsyncCopyVariable<bool> var_device_policy_is_loaded_{"policy_is_loaded", false}; - // Variables mapping the exposed methods from the policy::DevicePolicy. + // Variables mapping the exposed methods from the |policy::DevicePolicy|. AsyncCopyVariable<std::string> var_release_channel_{"release_channel"}; AsyncCopyVariable<bool> var_release_channel_delegated_{ "release_channel_delegated"}; + AsyncCopyVariable<std::string> var_release_lts_tag_{"release_lts_tag"}; AsyncCopyVariable<bool> var_update_disabled_{"update_disabled"}; AsyncCopyVariable<std::string> var_target_version_prefix_{ "target_version_prefix"}; @@ -196,7 +225,7 @@ class RealDevicePolicyProvider : public DevicePolicyProvider { AsyncCopyVariable<std::set<chromeos_update_engine::ConnectionType>> var_allowed_connection_types_for_update_{ "allowed_connection_types_for_update"}; - AsyncCopyVariable<std::string> var_owner_{"owner"}; + AsyncCopyVariable<bool> var_has_owner_{"owner"}; AsyncCopyVariable<bool> var_http_downloads_enabled_{"http_downloads_enabled"}; AsyncCopyVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled"}; AsyncCopyVariable<bool> var_allow_kiosk_app_control_chrome_version_{ @@ -205,6 +234,12 @@ class RealDevicePolicyProvider : public DevicePolicyProvider { "update_time_restrictions"}; AsyncCopyVariable<std::string> var_auto_launched_kiosk_app_id_{ "auto_launched_kiosk_app_id"}; + AsyncCopyVariable<ChannelDowngradeBehavior> var_channel_downgrade_behavior_{ + "channel_downgrade_behavior"}; + AsyncCopyVariable<base::Version> var_device_minimum_version_{ + "device_minimum_version"}; + AsyncCopyVariable<std::string> var_quick_fix_build_token_{ + "quick_fix_build_token"}; DISALLOW_COPY_AND_ASSIGN(RealDevicePolicyProvider); }; diff --git a/update_manager/real_device_policy_provider_unittest.cc b/update_manager/real_device_policy_provider_unittest.cc index 0d7b0d0d..fd558594 100644 --- a/update_manager/real_device_policy_provider_unittest.cc +++ b/update_manager/real_device_policy_provider_unittest.cc @@ -34,7 +34,7 @@ #include "update_engine/common/test_utils.h" #if USE_DBUS -#include "update_engine/dbus_test_utils.h" +#include "update_engine/cros/dbus_test_utils.h" #endif // USE_DBUS #include "update_engine/update_manager/umtest_utils.h" @@ -177,6 +177,7 @@ TEST_F(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyEmptyVariables) { UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel()); UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel_delegated()); + UmTestUtils::ExpectVariableNotSet(provider_->var_release_lts_tag()); UmTestUtils::ExpectVariableNotSet(provider_->var_update_disabled()); UmTestUtils::ExpectVariableNotSet(provider_->var_target_version_prefix()); UmTestUtils::ExpectVariableNotSet( @@ -186,7 +187,7 @@ TEST_F(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyEmptyVariables) { UmTestUtils::ExpectVariableNotSet(provider_->var_scatter_factor()); UmTestUtils::ExpectVariableNotSet( provider_->var_allowed_connection_types_for_update()); - UmTestUtils::ExpectVariableNotSet(provider_->var_owner()); + UmTestUtils::ExpectVariableNotSet(provider_->var_has_owner()); UmTestUtils::ExpectVariableNotSet(provider_->var_http_downloads_enabled()); UmTestUtils::ExpectVariableNotSet(provider_->var_au_p2p_enabled()); UmTestUtils::ExpectVariableNotSet( @@ -194,6 +195,9 @@ TEST_F(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyEmptyVariables) { UmTestUtils::ExpectVariableNotSet( provider_->var_auto_launched_kiosk_app_id()); UmTestUtils::ExpectVariableNotSet(provider_->var_disallowed_time_intervals()); + UmTestUtils::ExpectVariableNotSet( + provider_->var_channel_downgrade_behavior()); + UmTestUtils::ExpectVariableNotSet(provider_->var_quick_fix_build_token()); } TEST_F(UmRealDevicePolicyProviderTest, ValuesUpdated) { @@ -230,6 +234,26 @@ TEST_F(UmRealDevicePolicyProviderTest, ValuesUpdated) { string("myapp"), provider_->var_auto_launched_kiosk_app_id()); } +TEST_F(UmRealDevicePolicyProviderTest, HasOwnerConverted) { + SetUpExistentDevicePolicy(); + EXPECT_TRUE(provider_->Init()); + loop_.RunOnce(false); + Mock::VerifyAndClearExpectations(&mock_policy_provider_); + + EXPECT_CALL(mock_device_policy_, GetOwner(_)) + .Times(2) + .WillOnce(DoAll(SetArgPointee<0>(string("")), Return(true))) + .WillOnce(DoAll(SetArgPointee<0>(string("abc@test.org")), Return(true))); + + // Enterprise enrolled device. + provider_->RefreshDevicePolicy(); + UmTestUtils::ExpectVariableHasValue(false, provider_->var_has_owner()); + + // Has a device owner. + provider_->RefreshDevicePolicy(); + UmTestUtils::ExpectVariableHasValue(true, provider_->var_has_owner()); +} + TEST_F(UmRealDevicePolicyProviderTest, RollbackToTargetVersionConverted) { SetUpExistentDevicePolicy(); EXPECT_CALL(mock_device_policy_, GetRollbackToTargetVersion(_)) @@ -324,14 +348,14 @@ TEST_F(UmRealDevicePolicyProviderTest, AllowedTypesConverted) { #else .Times(1) #endif // USE_DBUS - .WillRepeatedly(DoAll( - SetArgPointee<0>(set<string>{"bluetooth", "wifi", "not-a-type"}), - Return(true))); + .WillRepeatedly( + DoAll(SetArgPointee<0>(set<string>{"ethernet", "wifi", "not-a-type"}), + Return(true))); EXPECT_TRUE(provider_->Init()); loop_.RunOnce(false); UmTestUtils::ExpectVariableHasValue( - set<ConnectionType>{ConnectionType::kWifi, ConnectionType::kBluetooth}, + set<ConnectionType>{ConnectionType::kWifi, ConnectionType::kEthernet}, provider_->var_allowed_connection_types_for_update()); } @@ -356,4 +380,83 @@ TEST_F(UmRealDevicePolicyProviderTest, DisallowedIntervalsConverted) { provider_->var_disallowed_time_intervals()); } +TEST_F(UmRealDevicePolicyProviderTest, ChannelDowngradeBehaviorConverted) { + SetUpExistentDevicePolicy(); + EXPECT_CALL(mock_device_policy_, GetChannelDowngradeBehavior(_)) +#if USE_DBUS + .Times(2) +#else + .Times(1) +#endif // USE_DBUS + .WillRepeatedly(DoAll(SetArgPointee<0>(static_cast<int>( + ChannelDowngradeBehavior::kRollback)), + Return(true))); + EXPECT_TRUE(provider_->Init()); + loop_.RunOnce(false); + + UmTestUtils::ExpectVariableHasValue( + ChannelDowngradeBehavior::kRollback, + provider_->var_channel_downgrade_behavior()); +} + +TEST_F(UmRealDevicePolicyProviderTest, ChannelDowngradeBehaviorTooSmall) { + SetUpExistentDevicePolicy(); + EXPECT_CALL(mock_device_policy_, GetChannelDowngradeBehavior(_)) +#if USE_DBUS + .Times(2) +#else + .Times(1) +#endif // USE_DBUS + .WillRepeatedly(DoAll(SetArgPointee<0>(-1), Return(true))); + EXPECT_TRUE(provider_->Init()); + loop_.RunOnce(false); + + UmTestUtils::ExpectVariableNotSet( + provider_->var_channel_downgrade_behavior()); +} + +TEST_F(UmRealDevicePolicyProviderTest, ChannelDowngradeBehaviorTooLarge) { + SetUpExistentDevicePolicy(); + EXPECT_CALL(mock_device_policy_, GetChannelDowngradeBehavior(_)) +#if USE_DBUS + .Times(2) +#else + .Times(1) +#endif // USE_DBUS + .WillRepeatedly(DoAll(SetArgPointee<0>(10), Return(true))); + EXPECT_TRUE(provider_->Init()); + loop_.RunOnce(false); + + UmTestUtils::ExpectVariableNotSet( + provider_->var_channel_downgrade_behavior()); +} + +TEST_F(UmRealDevicePolicyProviderTest, DeviceMinimumVersionPolicySet) { + SetUpExistentDevicePolicy(); + + base::Version device_minimum_version("13315.60.12"); + + EXPECT_CALL(mock_device_policy_, GetHighestDeviceMinimumVersion(_)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(device_minimum_version), Return(true))); + EXPECT_TRUE(provider_->Init()); + loop_.RunOnce(false); + + UmTestUtils::ExpectVariableHasValue(device_minimum_version, + provider_->var_device_minimum_version()); +} + +TEST_F(UmRealDevicePolicyProviderTest, DeviceQuickFixBuildTokenSet) { + SetUpExistentDevicePolicy(); + + EXPECT_CALL(mock_device_policy_, GetDeviceQuickFixBuildToken(_)) + .WillRepeatedly( + DoAll(SetArgPointee<0>(string("some_token")), Return(true))); + EXPECT_TRUE(provider_->Init()); + loop_.RunOnce(false); + + UmTestUtils::ExpectVariableHasValue(string("some_token"), + provider_->var_quick_fix_build_token()); +} + } // namespace chromeos_update_manager diff --git a/update_manager/real_shill_provider.cc b/update_manager/real_shill_provider.cc index 01446038..4d067fd4 100644 --- a/update_manager/real_shill_provider.cc +++ b/update_manager/real_shill_provider.cc @@ -24,6 +24,7 @@ #include <shill/dbus-constants.h> #include <shill/dbus-proxies.h> +using chromeos_update_engine::SystemState; using chromeos_update_engine::connection_utils::ParseConnectionType; using org::chromium::flimflam::ManagerProxyInterface; using org::chromium::flimflam::ServiceProxyInterface; @@ -97,7 +98,8 @@ bool RealShillProvider::ProcessDefaultService( bool is_connected = (default_service_path_.IsValid() && default_service_path_.value() != "/"); var_is_connected_.SetValue(is_connected); - var_conn_last_changed_.SetValue(clock_->GetWallclockTime()); + var_conn_last_changed_.SetValue( + SystemState::Get()->clock()->GetWallclockTime()); if (!is_connected) { var_conn_type_.UnsetValue(); diff --git a/update_manager/real_shill_provider.h b/update_manager/real_shill_provider.h index ec5c5701..cd53d920 100644 --- a/update_manager/real_shill_provider.h +++ b/update_manager/real_shill_provider.h @@ -27,8 +27,8 @@ #include <base/time/time.h> #include <dbus/object_path.h> -#include "update_engine/common/clock_interface.h" -#include "update_engine/shill_proxy_interface.h" +#include "update_engine/common/system_state.h" +#include "update_engine/cros/shill_proxy_interface.h" #include "update_engine/update_manager/generic_variables.h" #include "update_engine/update_manager/shill_provider.h" @@ -37,9 +37,9 @@ namespace chromeos_update_manager { // ShillProvider concrete implementation. class RealShillProvider : public ShillProvider { public: - RealShillProvider(chromeos_update_engine::ShillProxyInterface* shill_proxy, - chromeos_update_engine::ClockInterface* clock) - : shill_proxy_(shill_proxy), clock_(clock) {} + explicit RealShillProvider( + chromeos_update_engine::ShillProxyInterface* shill_proxy) + : shill_proxy_(shill_proxy) {} ~RealShillProvider() override = default; @@ -81,9 +81,6 @@ class RealShillProvider : public ShillProvider { // The mockable interface to access the shill DBus proxies. std::unique_ptr<chromeos_update_engine::ShillProxyInterface> shill_proxy_; - // A clock abstraction (mockable). - chromeos_update_engine::ClockInterface* const clock_; - // The provider's variables. AsyncCopyVariable<bool> var_is_connected_{"is_connected"}; AsyncCopyVariable<chromeos_update_engine::ConnectionType> var_conn_type_{ diff --git a/update_manager/real_shill_provider_unittest.cc b/update_manager/real_shill_provider_unittest.cc index dcc729ab..9a2d8a80 100644 --- a/update_manager/real_shill_provider_unittest.cc +++ b/update_manager/real_shill_provider_unittest.cc @@ -27,17 +27,17 @@ #include <shill/dbus-proxies.h> #include <shill/dbus-proxy-mocks.h> -#include "update_engine/common/fake_clock.h" #include "update_engine/common/test_utils.h" -#include "update_engine/dbus_test_utils.h" -#include "update_engine/fake_shill_proxy.h" +#include "update_engine/cros/dbus_test_utils.h" +#include "update_engine/cros/fake_shill_proxy.h" +#include "update_engine/cros/fake_system_state.h" #include "update_engine/update_manager/umtest_utils.h" using base::Time; using base::TimeDelta; using chromeos_update_engine::ConnectionTethering; using chromeos_update_engine::ConnectionType; -using chromeos_update_engine::FakeClock; +using chromeos_update_engine::FakeSystemState; using org::chromium::flimflam::ManagerProxyMock; using org::chromium::flimflam::ServiceProxyMock; using std::unique_ptr; @@ -51,8 +51,6 @@ namespace { // Fake service paths. const char* const kFakeEthernetServicePath = "/fake/ethernet/service"; const char* const kFakeWifiServicePath = "/fake/wifi/service"; -const char* const kFakeWimaxServicePath = "/fake/wimax/service"; -const char* const kFakeBluetoothServicePath = "/fake/bluetooth/service"; const char* const kFakeCellularServicePath = "/fake/cellular/service"; const char* const kFakeVpnServicePath = "/fake/vpn/service"; const char* const kFakeUnknownServicePath = "/fake/unknown/service"; @@ -65,10 +63,10 @@ class UmRealShillProviderTest : public ::testing::Test { protected: // Initialize the RealShillProvider under test. void SetUp() override { - fake_clock_.SetWallclockTime(InitTime()); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(InitTime()); loop_.SetAsCurrent(); fake_shill_proxy_ = new chromeos_update_engine::FakeShillProxy(); - provider_.reset(new RealShillProvider(fake_shill_proxy_, &fake_clock_)); + provider_.reset(new RealShillProvider(fake_shill_proxy_)); ManagerProxyMock* manager_proxy_mock = fake_shill_proxy_->GetManagerProxy(); @@ -129,11 +127,12 @@ class UmRealShillProviderTest : public ::testing::Test { void SendDefaultServiceSignal(const std::string& service_path, Time* conn_change_time_p) { const Time conn_change_time = ConnChangedTime(); - fake_clock_.SetWallclockTime(conn_change_time); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(conn_change_time); ASSERT_TRUE(manager_property_changed_.IsHandlerRegistered()); manager_property_changed_.signal_callback().Run( shill::kDefaultServiceProperty, dbus::ObjectPath(service_path)); - fake_clock_.SetWallclockTime(conn_change_time + TimeDelta::FromSeconds(5)); + FakeSystemState::Get()->fake_clock()->SetWallclockTime( + conn_change_time + TimeDelta::FromSeconds(5)); if (conn_change_time_p) *conn_change_time_p = conn_change_time; } @@ -204,7 +203,6 @@ class UmRealShillProviderTest : public ::testing::Test { } brillo::FakeMessageLoop loop_{nullptr}; - FakeClock fake_clock_; chromeos_update_engine::FakeShillProxy* fake_shill_proxy_; // The registered signal handler for the signal Manager.PropertyChanged. @@ -317,21 +315,6 @@ TEST_F(UmRealShillProviderTest, ReadConnTypeWifi) { kFakeWifiServicePath, shill::kTypeWifi, ConnectionType::kWifi); } -// Test that Wimax connection is identified correctly. -TEST_F(UmRealShillProviderTest, ReadConnTypeWimax) { - InitWithDefaultService("/"); - SetupConnectionAndTestType( - kFakeWimaxServicePath, shill::kTypeWimax, ConnectionType::kWimax); -} - -// Test that Bluetooth connection is identified correctly. -TEST_F(UmRealShillProviderTest, ReadConnTypeBluetooth) { - InitWithDefaultService("/"); - SetupConnectionAndTestType(kFakeBluetoothServicePath, - shill::kTypeBluetooth, - ConnectionType::kBluetooth); -} - // Test that Cellular connection is identified correctly. TEST_F(UmRealShillProviderTest, ReadConnTypeCellular) { InitWithDefaultService("/"); diff --git a/update_manager/real_system_provider.cc b/update_manager/real_system_provider.cc index a900071a..34397f33 100644 --- a/update_manager/real_system_provider.cc +++ b/update_manager/real_system_provider.cc @@ -20,14 +20,17 @@ #include <base/callback.h> #include <base/logging.h> #include <base/time/time.h> -#if USE_CHROME_KIOSK_APP #include <kiosk-app/dbus-proxies.h> -#endif // USE_CHROME_KIOSK_APP +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/hardware_interface.h" +#include "update_engine/common/system_state.h" #include "update_engine/common/utils.h" +#include "update_engine/cros/omaha_request_params.h" #include "update_engine/update_manager/generic_variables.h" #include "update_engine/update_manager/variable.h" +using chromeos_update_engine::SystemState; using std::string; namespace chromeos_update_manager { @@ -64,9 +67,10 @@ class RetryPollVariable : public Variable<T> { std::unique_ptr<T> result(new T()); if (!func_.Run(result.get())) { if (failed_attempts_ >= kRetryPollVariableMaxRetry) { - // Give up on the retries, set back the desired polling interval and - // return the default. + // Give up on the retries and set back the desired polling interval. this->SetPollInterval(base_interval_); + // Release the result instead of returning a |nullptr| to indicate that + // the result could not be fetched. return result.release(); } this->SetPollInterval( @@ -96,19 +100,20 @@ class RetryPollVariable : public Variable<T> { bool RealSystemProvider::Init() { var_is_normal_boot_mode_.reset(new ConstCopyVariable<bool>( - "is_normal_boot_mode", hardware_->IsNormalBootMode())); + "is_normal_boot_mode", + SystemState::Get()->hardware()->IsNormalBootMode())); var_is_official_build_.reset(new ConstCopyVariable<bool>( - "is_official_build", hardware_->IsOfficialBuild())); + "is_official_build", SystemState::Get()->hardware()->IsOfficialBuild())); var_is_oobe_complete_.reset(new CallCopyVariable<bool>( "is_oobe_complete", base::Bind(&chromeos_update_engine::HardwareInterface::IsOOBEComplete, - base::Unretained(hardware_), + base::Unretained(SystemState::Get()->hardware()), nullptr))); var_num_slots_.reset(new ConstCopyVariable<unsigned int>( - "num_slots", boot_control_->GetNumSlots())); + "num_slots", SystemState::Get()->boot_control()->GetNumSlots())); var_kiosk_required_platform_version_.reset(new RetryPollVariable<string>( "kiosk_required_platform_version", @@ -116,12 +121,15 @@ bool RealSystemProvider::Init() { base::Bind(&RealSystemProvider::GetKioskAppRequiredPlatformVersion, base::Unretained(this)))); + var_chromeos_version_.reset(new ConstCopyVariable<base::Version>( + "chromeos_version", + base::Version(SystemState::Get()->request_params()->app_version()))); + return true; } bool RealSystemProvider::GetKioskAppRequiredPlatformVersion( string* required_platform_version) { -#if USE_CHROME_KIOSK_APP brillo::ErrorPtr error; if (!kiosk_app_proxy_->GetRequiredPlatformVersion(required_platform_version, &error)) { @@ -129,7 +137,6 @@ bool RealSystemProvider::GetKioskAppRequiredPlatformVersion( required_platform_version->clear(); return false; } -#endif // USE_CHROME_KIOSK_APP return true; } diff --git a/update_manager/real_system_provider.h b/update_manager/real_system_provider.h index 114c6eab..558d3be4 100644 --- a/update_manager/real_system_provider.h +++ b/update_manager/real_system_provider.h @@ -20,8 +20,8 @@ #include <memory> #include <string> -#include "update_engine/common/boot_control_interface.h" -#include "update_engine/common/hardware_interface.h" +#include <base/version.h> + #include "update_engine/update_manager/system_provider.h" namespace org { @@ -36,18 +36,8 @@ namespace chromeos_update_manager { class RealSystemProvider : public SystemProvider { public: RealSystemProvider( - chromeos_update_engine::HardwareInterface* hardware, - chromeos_update_engine::BootControlInterface* boot_control, org::chromium::KioskAppServiceInterfaceProxyInterface* kiosk_app_proxy) - : hardware_(hardware), -#if USE_CHROME_KIOSK_APP - boot_control_(boot_control), - kiosk_app_proxy_(kiosk_app_proxy) { - } -#else - boot_control_(boot_control) { - } -#endif // USE_CHROME_KIOSK_APP + : kiosk_app_proxy_(kiosk_app_proxy) {} // Initializes the provider and returns whether it succeeded. bool Init(); @@ -72,6 +62,10 @@ class RealSystemProvider : public SystemProvider { return var_kiosk_required_platform_version_.get(); } + Variable<base::Version>* var_chromeos_version() override { + return var_chromeos_version_.get(); + } + private: bool GetKioskAppRequiredPlatformVersion( std::string* required_platform_version); @@ -81,12 +75,9 @@ class RealSystemProvider : public SystemProvider { std::unique_ptr<Variable<bool>> var_is_oobe_complete_; std::unique_ptr<Variable<unsigned int>> var_num_slots_; std::unique_ptr<Variable<std::string>> var_kiosk_required_platform_version_; + std::unique_ptr<Variable<base::Version>> var_chromeos_version_; - chromeos_update_engine::HardwareInterface* const hardware_; - chromeos_update_engine::BootControlInterface* const boot_control_; -#if USE_CHROME_KIOSK_APP org::chromium::KioskAppServiceInterfaceProxyInterface* const kiosk_app_proxy_; -#endif // USE_CHROME_KIOSK_APP DISALLOW_COPY_AND_ASSIGN(RealSystemProvider); }; diff --git a/update_manager/real_system_provider_unittest.cc b/update_manager/real_system_provider_unittest.cc index f654f7ac..9abcad06 100644 --- a/update_manager/real_system_provider_unittest.cc +++ b/update_manager/real_system_provider_unittest.cc @@ -21,55 +21,44 @@ #include <base/time/time.h> #include <gmock/gmock.h> #include <gtest/gtest.h> +#include <kiosk-app/dbus-proxies.h> +#include <kiosk-app/dbus-proxy-mocks.h> #include "update_engine/common/fake_boot_control.h" #include "update_engine/common/fake_hardware.h" +#include "update_engine/cros/fake_system_state.h" #include "update_engine/update_manager/umtest_utils.h" -#if USE_CHROME_KIOSK_APP -#include "kiosk-app/dbus-proxies.h" -#include "kiosk-app/dbus-proxy-mocks.h" +using chromeos_update_engine::FakeSystemState; using org::chromium::KioskAppServiceInterfaceProxyMock; -#endif // USE_CHROME_KIOSK_APP using std::unique_ptr; using testing::_; using testing::DoAll; using testing::Return; using testing::SetArgPointee; -#if USE_CHROME_KIOSK_APP namespace { const char kRequiredPlatformVersion[] = "1234.0.0"; } // namespace -#endif // USE_CHROME_KIOSK_APP namespace chromeos_update_manager { class UmRealSystemProviderTest : public ::testing::Test { protected: void SetUp() override { -#if USE_CHROME_KIOSK_APP + FakeSystemState::CreateInstance(); kiosk_app_proxy_mock_.reset(new KioskAppServiceInterfaceProxyMock()); ON_CALL(*kiosk_app_proxy_mock_, GetRequiredPlatformVersion(_, _, _)) .WillByDefault( DoAll(SetArgPointee<0>(kRequiredPlatformVersion), Return(true))); - provider_.reset(new RealSystemProvider( - &fake_hardware_, &fake_boot_control_, kiosk_app_proxy_mock_.get())); -#else - provider_.reset( - new RealSystemProvider(&fake_hardware_, &fake_boot_control_, nullptr)); -#endif // USE_CHROME_KIOSK_APP + provider_.reset(new RealSystemProvider(kiosk_app_proxy_mock_.get())); EXPECT_TRUE(provider_->Init()); } - chromeos_update_engine::FakeHardware fake_hardware_; - chromeos_update_engine::FakeBootControl fake_boot_control_; unique_ptr<RealSystemProvider> provider_; -#if USE_CHROME_KIOSK_APP unique_ptr<KioskAppServiceInterfaceProxyMock> kiosk_app_proxy_mock_; -#endif // USE_CHROME_KIOSK_APP }; TEST_F(UmRealSystemProviderTest, InitTest) { @@ -77,19 +66,29 @@ TEST_F(UmRealSystemProviderTest, InitTest) { EXPECT_NE(nullptr, provider_->var_is_official_build()); EXPECT_NE(nullptr, provider_->var_is_oobe_complete()); EXPECT_NE(nullptr, provider_->var_kiosk_required_platform_version()); + EXPECT_NE(nullptr, provider_->var_chromeos_version()); } TEST_F(UmRealSystemProviderTest, IsOOBECompleteTrue) { - fake_hardware_.SetIsOOBEComplete(base::Time()); + FakeSystemState::Get()->fake_hardware()->SetIsOOBEComplete(base::Time()); UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_oobe_complete()); } TEST_F(UmRealSystemProviderTest, IsOOBECompleteFalse) { - fake_hardware_.UnsetIsOOBEComplete(); + FakeSystemState::Get()->fake_hardware()->UnsetIsOOBEComplete(); UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_oobe_complete()); } -#if USE_CHROME_KIOSK_APP +TEST_F(UmRealSystemProviderTest, VersionFromRequestParams) { + FakeSystemState::Get()->request_params()->set_app_version("1.2.3"); + // Call |Init| again to pick up the version. + EXPECT_TRUE(provider_->Init()); + + base::Version version("1.2.3"); + UmTestUtils::ExpectVariableHasValue(version, + provider_->var_chromeos_version()); +} + TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersion) { UmTestUtils::ExpectVariableHasValue( std::string(kRequiredPlatformVersion), @@ -119,11 +118,21 @@ TEST_F(UmRealSystemProviderTest, std::string(kRequiredPlatformVersion), provider_->var_kiosk_required_platform_version()); } -#else -TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersion) { + +TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersionRepeatedFailure) { + // Simulate unreadable platform version. The variable should return a + // null pointer |kRetryPollVariableMaxRetry| times and then return an empty + // string to indicate that it gave up. + constexpr int kNumMethodCalls = 5; + EXPECT_CALL(*kiosk_app_proxy_mock_, GetRequiredPlatformVersion) + .Times(kNumMethodCalls + 1) + .WillRepeatedly(Return(false)); + for (int i = 0; i < kNumMethodCalls; ++i) { + UmTestUtils::ExpectVariableNotSet( + provider_->var_kiosk_required_platform_version()); + } UmTestUtils::ExpectVariableHasValue( - std::string(), provider_->var_kiosk_required_platform_version()); + std::string(""), provider_->var_kiosk_required_platform_version()); } -#endif // USE_CHROME_KIOSK_APP } // namespace chromeos_update_manager diff --git a/update_manager/real_time_provider.cc b/update_manager/real_time_provider.cc index efd1747c..2b71fa0f 100644 --- a/update_manager/real_time_provider.cc +++ b/update_manager/real_time_provider.cc @@ -20,11 +20,11 @@ #include <base/time/time.h> -#include "update_engine/common/clock_interface.h" +#include "update_engine/common/system_state.h" using base::Time; using base::TimeDelta; -using chromeos_update_engine::ClockInterface; +using chromeos_update_engine::SystemState; using std::string; namespace chromeos_update_manager { @@ -34,13 +34,13 @@ class CurrDateVariable : public Variable<Time> { public: // TODO(garnold) Turn this into an async variable with the needed callback // logic for when it value changes. - CurrDateVariable(const string& name, ClockInterface* clock) - : Variable<Time>(name, TimeDelta::FromHours(1)), clock_(clock) {} + explicit CurrDateVariable(const string& name) + : Variable<Time>(name, TimeDelta::FromHours(1)) {} protected: virtual const Time* GetValue(TimeDelta /* timeout */, string* /* errmsg */) { Time::Exploded now_exp; - clock_->GetWallclockTime().LocalExplode(&now_exp); + SystemState::Get()->clock()->GetWallclockTime().LocalExplode(&now_exp); now_exp.hour = now_exp.minute = now_exp.second = now_exp.millisecond = 0; Time* now = new Time(); bool success = Time::FromLocalExploded(now_exp, now); @@ -49,8 +49,6 @@ class CurrDateVariable : public Variable<Time> { } private: - ClockInterface* clock_; - DISALLOW_COPY_AND_ASSIGN(CurrDateVariable); }; @@ -59,44 +57,40 @@ class CurrHourVariable : public Variable<int> { public: // TODO(garnold) Turn this into an async variable with the needed callback // logic for when it value changes. - CurrHourVariable(const string& name, ClockInterface* clock) - : Variable<int>(name, TimeDelta::FromMinutes(5)), clock_(clock) {} + explicit CurrHourVariable(const string& name) + : Variable<int>(name, TimeDelta::FromMinutes(5)) {} protected: virtual const int* GetValue(TimeDelta /* timeout */, string* /* errmsg */) { Time::Exploded exploded; - clock_->GetWallclockTime().LocalExplode(&exploded); + SystemState::Get()->clock()->GetWallclockTime().LocalExplode(&exploded); return new int(exploded.hour); } private: - ClockInterface* clock_; - DISALLOW_COPY_AND_ASSIGN(CurrHourVariable); }; class CurrMinuteVariable : public Variable<int> { public: - CurrMinuteVariable(const string& name, ClockInterface* clock) - : Variable<int>(name, TimeDelta::FromSeconds(15)), clock_(clock) {} + explicit CurrMinuteVariable(const string& name) + : Variable<int>(name, TimeDelta::FromSeconds(15)) {} protected: virtual const int* GetValue(TimeDelta /* timeout */, string* /* errmsg */) { Time::Exploded exploded; - clock_->GetWallclockTime().LocalExplode(&exploded); + SystemState::Get()->clock()->GetWallclockTime().LocalExplode(&exploded); return new int(exploded.minute); } private: - ClockInterface* clock_; - DISALLOW_COPY_AND_ASSIGN(CurrMinuteVariable); }; bool RealTimeProvider::Init() { - var_curr_date_.reset(new CurrDateVariable("curr_date", clock_)); - var_curr_hour_.reset(new CurrHourVariable("curr_hour", clock_)); - var_curr_minute_.reset(new CurrMinuteVariable("curr_minute", clock_)); + var_curr_date_.reset(new CurrDateVariable("curr_date")); + var_curr_hour_.reset(new CurrHourVariable("curr_hour")); + var_curr_minute_.reset(new CurrMinuteVariable("curr_minute")); return true; } diff --git a/update_manager/real_time_provider.h b/update_manager/real_time_provider.h index 40dab36b..58b0fa5c 100644 --- a/update_manager/real_time_provider.h +++ b/update_manager/real_time_provider.h @@ -21,7 +21,6 @@ #include <base/time/time.h> -#include "update_engine/common/clock_interface.h" #include "update_engine/update_manager/time_provider.h" namespace chromeos_update_manager { @@ -29,8 +28,7 @@ namespace chromeos_update_manager { // TimeProvider concrete implementation. class RealTimeProvider : public TimeProvider { public: - explicit RealTimeProvider(chromeos_update_engine::ClockInterface* clock) - : clock_(clock) {} + RealTimeProvider() = default; // Initializes the provider and returns whether it succeeded. bool Init(); @@ -44,9 +42,6 @@ class RealTimeProvider : public TimeProvider { Variable<int>* var_curr_minute() override { return var_curr_minute_.get(); } private: - // A clock abstraction (fakeable). - chromeos_update_engine::ClockInterface* const clock_; - std::unique_ptr<Variable<base::Time>> var_curr_date_; std::unique_ptr<Variable<int>> var_curr_hour_; std::unique_ptr<Variable<int>> var_curr_minute_; diff --git a/update_manager/real_time_provider_unittest.cc b/update_manager/real_time_provider_unittest.cc index ce2a7182..f8ed0d2c 100644 --- a/update_manager/real_time_provider_unittest.cc +++ b/update_manager/real_time_provider_unittest.cc @@ -22,11 +22,11 @@ #include <base/time/time.h> #include <gtest/gtest.h> -#include "update_engine/common/fake_clock.h" +#include "update_engine/cros/fake_system_state.h" #include "update_engine/update_manager/umtest_utils.h" using base::Time; -using chromeos_update_engine::FakeClock; +using chromeos_update_engine::FakeSystemState; using std::unique_ptr; namespace chromeos_update_manager { @@ -34,8 +34,9 @@ namespace chromeos_update_manager { class UmRealTimeProviderTest : public ::testing::Test { protected: void SetUp() override { + FakeSystemState::CreateInstance(); // The provider initializes correctly. - provider_.reset(new RealTimeProvider(&fake_clock_)); + provider_.reset(new RealTimeProvider()); ASSERT_NE(nullptr, provider_.get()); ASSERT_TRUE(provider_->Init()); } @@ -56,7 +57,6 @@ class UmRealTimeProviderTest : public ::testing::Test { return time; } - FakeClock fake_clock_; unique_ptr<RealTimeProvider> provider_; }; @@ -71,7 +71,7 @@ TEST_F(UmRealTimeProviderTest, CurrDateValid) { Time expected; ignore_result(Time::FromLocalExploded(exploded, &expected)); - fake_clock_.SetWallclockTime(now); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(now); UmTestUtils::ExpectVariableHasValue(expected, provider_->var_curr_date()); } @@ -79,7 +79,7 @@ TEST_F(UmRealTimeProviderTest, CurrHourValid) { const Time now = CurrTime(); Time::Exploded expected; now.LocalExplode(&expected); - fake_clock_.SetWallclockTime(now); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(now); UmTestUtils::ExpectVariableHasValue(expected.hour, provider_->var_curr_hour()); } @@ -88,7 +88,7 @@ TEST_F(UmRealTimeProviderTest, CurrMinuteValid) { const Time now = CurrTime(); Time::Exploded expected; now.LocalExplode(&expected); - fake_clock_.SetWallclockTime(now); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(now); UmTestUtils::ExpectVariableHasValue(expected.minute, provider_->var_curr_minute()); } diff --git a/update_manager/real_updater_provider.cc b/update_manager/real_updater_provider.cc index 134db691..5b76332a 100644 --- a/update_manager/real_updater_provider.cc +++ b/update_manager/real_updater_provider.cc @@ -18,6 +18,7 @@ #include <inttypes.h> +#include <algorithm> #include <string> #include <base/bind.h> @@ -26,10 +27,10 @@ #include <update_engine/dbus-constants.h> #include "update_engine/client_library/include/update_engine/update_status.h" -#include "update_engine/common/clock_interface.h" #include "update_engine/common/prefs.h" -#include "update_engine/omaha_request_params.h" -#include "update_engine/update_attempter.h" +#include "update_engine/common/system_state.h" +#include "update_engine/cros/omaha_request_params.h" +#include "update_engine/cros/update_attempter.h" #include "update_engine/update_status_utils.h" using base::StringPrintf; @@ -48,25 +49,16 @@ namespace chromeos_update_manager { template <typename T> class UpdaterVariableBase : public Variable<T> { public: - UpdaterVariableBase(const string& name, - VariableMode mode, - SystemState* system_state) - : Variable<T>(name, mode), system_state_(system_state) {} - - protected: - // The system state used for pulling information from the updater. - inline SystemState* system_state() const { return system_state_; } - - private: - SystemState* const system_state_; + UpdaterVariableBase(const string& name, VariableMode mode) + : Variable<T>(name, mode) {} }; // Helper class for issuing a GetStatus() to the UpdateAttempter. class GetStatusHelper { public: - GetStatusHelper(SystemState* system_state, string* errmsg) { - is_success_ = - system_state->update_attempter()->GetStatus(&update_engine_status_); + explicit GetStatusHelper(string* errmsg) { + is_success_ = SystemState::Get()->update_attempter()->GetStatus( + &update_engine_status_); if (!is_success_ && errmsg) { *errmsg = "Failed to get a status update from the update engine"; } @@ -96,12 +88,12 @@ class GetStatusHelper { // A variable reporting the time when a last update check was issued. class LastCheckedTimeVariable : public UpdaterVariableBase<Time> { public: - LastCheckedTimeVariable(const string& name, SystemState* system_state) - : UpdaterVariableBase<Time>(name, kVariableModePoll, system_state) {} + explicit LastCheckedTimeVariable(const string& name) + : UpdaterVariableBase<Time>(name, kVariableModePoll) {} private: const Time* GetValue(TimeDelta /* timeout */, string* errmsg) override { - GetStatusHelper raw(system_state(), errmsg); + GetStatusHelper raw(errmsg); if (!raw.is_success()) return nullptr; @@ -115,12 +107,12 @@ class LastCheckedTimeVariable : public UpdaterVariableBase<Time> { // between 0.0 and 1.0. class ProgressVariable : public UpdaterVariableBase<double> { public: - ProgressVariable(const string& name, SystemState* system_state) - : UpdaterVariableBase<double>(name, kVariableModePoll, system_state) {} + explicit ProgressVariable(const string& name) + : UpdaterVariableBase<double>(name, kVariableModePoll) {} private: const double* GetValue(TimeDelta /* timeout */, string* errmsg) override { - GetStatusHelper raw(system_state(), errmsg); + GetStatusHelper raw(errmsg); if (!raw.is_success()) return nullptr; @@ -141,8 +133,8 @@ class ProgressVariable : public UpdaterVariableBase<double> { // A variable reporting the stage in which the update process is. class StageVariable : public UpdaterVariableBase<Stage> { public: - StageVariable(const string& name, SystemState* system_state) - : UpdaterVariableBase<Stage>(name, kVariableModePoll, system_state) {} + explicit StageVariable(const string& name) + : UpdaterVariableBase<Stage>(name, kVariableModePoll) {} private: struct CurrOpStrToStage { @@ -169,10 +161,12 @@ 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) { - GetStatusHelper raw(system_state(), errmsg); + GetStatusHelper raw(errmsg); if (!raw.is_success()) return nullptr; @@ -188,12 +182,12 @@ const Stage* StageVariable::GetValue(TimeDelta /* timeout */, string* errmsg) { // A variable reporting the version number that an update is updating to. class NewVersionVariable : public UpdaterVariableBase<string> { public: - NewVersionVariable(const string& name, SystemState* system_state) - : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {} + explicit NewVersionVariable(const string& name) + : UpdaterVariableBase<string>(name, kVariableModePoll) {} private: const string* GetValue(TimeDelta /* timeout */, string* errmsg) override { - GetStatusHelper raw(system_state(), errmsg); + GetStatusHelper raw(errmsg); if (!raw.is_success()) return nullptr; @@ -206,12 +200,12 @@ class NewVersionVariable : public UpdaterVariableBase<string> { // A variable reporting the size of the update being processed in bytes. class PayloadSizeVariable : public UpdaterVariableBase<uint64_t> { public: - PayloadSizeVariable(const string& name, SystemState* system_state) - : UpdaterVariableBase<uint64_t>(name, kVariableModePoll, system_state) {} + explicit PayloadSizeVariable(const string& name) + : UpdaterVariableBase<uint64_t>(name, kVariableModePoll) {} private: const uint64_t* GetValue(TimeDelta /* timeout */, string* errmsg) override { - GetStatusHelper raw(system_state(), errmsg); + GetStatusHelper raw(errmsg); if (!raw.is_success()) return nullptr; @@ -230,20 +224,20 @@ class PayloadSizeVariable : public UpdaterVariableBase<uint64_t> { // policy request. class UpdateCompletedTimeVariable : public UpdaterVariableBase<Time> { public: - UpdateCompletedTimeVariable(const string& name, SystemState* system_state) - : UpdaterVariableBase<Time>(name, kVariableModePoll, system_state) {} + explicit UpdateCompletedTimeVariable(const string& name) + : UpdaterVariableBase<Time>(name, kVariableModePoll) {} private: const Time* GetValue(TimeDelta /* timeout */, string* errmsg) override { Time update_boottime; - if (!system_state()->update_attempter()->GetBootTimeAtUpdate( + if (!SystemState::Get()->update_attempter()->GetBootTimeAtUpdate( &update_boottime)) { if (errmsg) *errmsg = "Update completed time could not be read"; return nullptr; } - chromeos_update_engine::ClockInterface* clock = system_state()->clock(); + const auto* clock = SystemState::Get()->clock(); Time curr_boottime = clock->GetBootTime(); if (curr_boottime < update_boottime) { if (errmsg) @@ -260,12 +254,12 @@ class UpdateCompletedTimeVariable : public UpdaterVariableBase<Time> { // Variables reporting the current image channel. class CurrChannelVariable : public UpdaterVariableBase<string> { public: - CurrChannelVariable(const string& name, SystemState* system_state) - : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {} + explicit CurrChannelVariable(const string& name) + : UpdaterVariableBase<string>(name, kVariableModePoll) {} private: const string* GetValue(TimeDelta /* timeout */, string* errmsg) override { - OmahaRequestParams* request_params = system_state()->request_params(); + OmahaRequestParams* request_params = SystemState::Get()->request_params(); string channel = request_params->current_channel(); if (channel.empty()) { if (errmsg) @@ -281,12 +275,12 @@ class CurrChannelVariable : public UpdaterVariableBase<string> { // Variables reporting the new image channel. class NewChannelVariable : public UpdaterVariableBase<string> { public: - NewChannelVariable(const string& name, SystemState* system_state) - : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {} + explicit NewChannelVariable(const string& name) + : UpdaterVariableBase<string>(name, kVariableModePoll) {} private: const string* GetValue(TimeDelta /* timeout */, string* errmsg) override { - OmahaRequestParams* request_params = system_state()->request_params(); + OmahaRequestParams* request_params = SystemState::Get()->request_params(); string channel = request_params->target_channel(); if (channel.empty()) { if (errmsg) @@ -305,24 +299,25 @@ class BooleanPrefVariable public chromeos_update_engine::PrefsInterface::ObserverInterface { public: BooleanPrefVariable(const string& name, - chromeos_update_engine::PrefsInterface* prefs, const char* key, bool default_val) : AsyncCopyVariable<bool>(name), - prefs_(prefs), key_(key), default_val_(default_val) { - prefs->AddObserver(key, this); + SystemState::Get()->prefs()->AddObserver(key, this); OnPrefSet(key); } - ~BooleanPrefVariable() { prefs_->RemoveObserver(key_, this); } + ~BooleanPrefVariable() { + SystemState::Get()->prefs()->RemoveObserver(key_, this); + } private: // Reads the actual value from the Prefs instance and updates the Variable // value. void OnPrefSet(const string& key) override { bool result = default_val_; - if (prefs_ && prefs_->Exists(key_) && !prefs_->GetBoolean(key_, &result)) + auto* prefs = SystemState::Get()->prefs(); + if (prefs->Exists(key_) && !prefs->GetBoolean(key_, &result)) result = default_val_; // AsyncCopyVariable will take care of values that didn't change. SetValue(result); @@ -330,8 +325,6 @@ class BooleanPrefVariable void OnPrefDeleted(const string& key) override { SetValue(default_val_); } - chromeos_update_engine::PrefsInterface* prefs_; - // The Boolean preference key and default value. const char* const key_; const bool default_val_; @@ -343,16 +336,16 @@ class BooleanPrefVariable class ConsecutiveFailedUpdateChecksVariable : public UpdaterVariableBase<unsigned int> { public: - ConsecutiveFailedUpdateChecksVariable(const string& name, - SystemState* system_state) - : UpdaterVariableBase<unsigned int>( - name, kVariableModePoll, system_state) {} + explicit ConsecutiveFailedUpdateChecksVariable(const string& name) + : UpdaterVariableBase<unsigned int>(name, kVariableModePoll) {} private: const unsigned int* GetValue(TimeDelta /* timeout */, string* /* errmsg */) override { - return new unsigned int( - system_state()->update_attempter()->consecutive_failed_update_checks()); + // NOLINTNEXTLINE(readability/casting) + return new unsigned int(SystemState::Get() + ->update_attempter() + ->consecutive_failed_update_checks()); } DISALLOW_COPY_AND_ASSIGN(ConsecutiveFailedUpdateChecksVariable); @@ -362,16 +355,16 @@ class ConsecutiveFailedUpdateChecksVariable class ServerDictatedPollIntervalVariable : public UpdaterVariableBase<unsigned int> { public: - ServerDictatedPollIntervalVariable(const string& name, - SystemState* system_state) - : UpdaterVariableBase<unsigned int>( - name, kVariableModePoll, system_state) {} + explicit ServerDictatedPollIntervalVariable(const string& name) + : UpdaterVariableBase<unsigned int>(name, kVariableModePoll) {} private: const unsigned int* GetValue(TimeDelta /* timeout */, string* /* errmsg */) override { - return new unsigned int( - system_state()->update_attempter()->server_dictated_poll_interval()); + // NOLINTNEXTLINE(readability/casting) + return new unsigned int(SystemState::Get() + ->update_attempter() + ->server_dictated_poll_interval()); } DISALLOW_COPY_AND_ASSIGN(ServerDictatedPollIntervalVariable); @@ -381,10 +374,10 @@ class ServerDictatedPollIntervalVariable class ForcedUpdateRequestedVariable : public UpdaterVariableBase<UpdateRequestStatus> { public: - ForcedUpdateRequestedVariable(const string& name, SystemState* system_state) + explicit ForcedUpdateRequestedVariable(const string& name) : UpdaterVariableBase<UpdateRequestStatus>::UpdaterVariableBase( - name, kVariableModeAsync, system_state) { - system_state->update_attempter()->set_forced_update_pending_callback( + name, kVariableModeAsync) { + SystemState::Get()->update_attempter()->set_forced_update_pending_callback( new base::Callback<void(bool, bool)>( // NOLINT(readability/function) base::Bind(&ForcedUpdateRequestedVariable::Reset, base::Unretained(this)))); @@ -416,15 +409,14 @@ class ForcedUpdateRequestedVariable class UpdateRestrictionsVariable : public UpdaterVariableBase<UpdateRestrictions> { public: - UpdateRestrictionsVariable(const string& name, SystemState* system_state) - : UpdaterVariableBase<UpdateRestrictions>( - name, kVariableModePoll, system_state) {} + explicit UpdateRestrictionsVariable(const string& name) + : UpdaterVariableBase<UpdateRestrictions>(name, kVariableModePoll) {} private: const UpdateRestrictions* GetValue(TimeDelta /* timeout */, string* /* errmsg */) override { UpdateAttemptFlags attempt_flags = - system_state()->update_attempter()->GetCurrentUpdateAttemptFlags(); + SystemState::Get()->update_attempter()->GetCurrentUpdateAttemptFlags(); UpdateRestrictions restriction_flags = UpdateRestrictions::kNone; // Don't blindly copy the whole value, test and set bits that should // transfer from one set of flags to the other. @@ -439,39 +431,73 @@ class UpdateRestrictionsVariable DISALLOW_COPY_AND_ASSIGN(UpdateRestrictionsVariable); }; +// A variable class for reading timeout interval prefs value. +class TestUpdateCheckIntervalTimeoutVariable : public Variable<int64_t> { + public: + explicit TestUpdateCheckIntervalTimeoutVariable(const string& name) + : Variable<int64_t>(name, kVariableModePoll), read_count_(0) { + SetMissingOk(); + } + ~TestUpdateCheckIntervalTimeoutVariable() = default; + + private: + const int64_t* GetValue(TimeDelta /* timeout */, + string* /* errmsg */) override { + auto key = chromeos_update_engine::kPrefsTestUpdateCheckIntervalTimeout; + auto* prefs = SystemState::Get()->prefs(); + int64_t result; + if (prefs->Exists(key) && prefs->GetInt64(key, &result)) { + // This specific value is used for testing only. So it should not be kept + // around and should be deleted after a few reads. + if (++read_count_ > 5) + prefs->Delete(key); + + // Limit the timeout interval to 10 minutes so it is not abused if it is + // seen on official images. + return new int64_t(std::min(result, static_cast<int64_t>(10 * 60))); + } + return nullptr; + } + + // Counts how many times this variable is read. This is used to delete the + // underlying file defining the variable after a certain number of reads in + // order to prevent any abuse of this variable. + int read_count_; + + DISALLOW_COPY_AND_ASSIGN(TestUpdateCheckIntervalTimeoutVariable); +}; + // RealUpdaterProvider methods. -RealUpdaterProvider::RealUpdaterProvider(SystemState* system_state) - : system_state_(system_state), - var_updater_started_time_("updater_started_time", - system_state->clock()->GetWallclockTime()), - var_last_checked_time_( - new LastCheckedTimeVariable("last_checked_time", system_state_)), - var_update_completed_time_(new UpdateCompletedTimeVariable( - "update_completed_time", system_state_)), - var_progress_(new ProgressVariable("progress", system_state_)), - var_stage_(new StageVariable("stage", system_state_)), - var_new_version_(new NewVersionVariable("new_version", system_state_)), - var_payload_size_(new PayloadSizeVariable("payload_size", system_state_)), - var_curr_channel_(new CurrChannelVariable("curr_channel", system_state_)), - var_new_channel_(new NewChannelVariable("new_channel", system_state_)), - var_p2p_enabled_( - new BooleanPrefVariable("p2p_enabled", - system_state_->prefs(), - chromeos_update_engine::kPrefsP2PEnabled, - false)), +RealUpdaterProvider::RealUpdaterProvider() + : var_updater_started_time_( + "updater_started_time", + SystemState::Get()->clock()->GetWallclockTime()), + var_last_checked_time_(new LastCheckedTimeVariable("last_checked_time")), + var_update_completed_time_( + new UpdateCompletedTimeVariable("update_completed_time")), + var_progress_(new ProgressVariable("progress")), + var_stage_(new StageVariable("stage")), + var_new_version_(new NewVersionVariable("new_version")), + var_payload_size_(new PayloadSizeVariable("payload_size")), + var_curr_channel_(new CurrChannelVariable("curr_channel")), + var_new_channel_(new NewChannelVariable("new_channel")), + var_p2p_enabled_(new BooleanPrefVariable( + "p2p_enabled", chromeos_update_engine::kPrefsP2PEnabled, false)), var_cellular_enabled_(new BooleanPrefVariable( "cellular_enabled", - system_state_->prefs(), chromeos_update_engine::kPrefsUpdateOverCellularPermission, false)), var_consecutive_failed_update_checks_( new ConsecutiveFailedUpdateChecksVariable( - "consecutive_failed_update_checks", system_state_)), + "consecutive_failed_update_checks")), var_server_dictated_poll_interval_(new ServerDictatedPollIntervalVariable( - "server_dictated_poll_interval", system_state_)), - var_forced_update_requested_(new ForcedUpdateRequestedVariable( - "forced_update_requested", system_state_)), - var_update_restrictions_(new UpdateRestrictionsVariable( - "update_restrictions", system_state_)) {} + "server_dictated_poll_interval")), + var_forced_update_requested_( + new ForcedUpdateRequestedVariable("forced_update_requested")), + var_update_restrictions_( + new UpdateRestrictionsVariable("update_restrictions")), + var_test_update_check_interval_timeout_( + new TestUpdateCheckIntervalTimeoutVariable( + "test_update_check_interval_timeout")) {} } // namespace chromeos_update_manager diff --git a/update_manager/real_updater_provider.h b/update_manager/real_updater_provider.h index 1b468956..24298d77 100644 --- a/update_manager/real_updater_provider.h +++ b/update_manager/real_updater_provider.h @@ -20,7 +20,6 @@ #include <memory> #include <string> -#include "update_engine/system_state.h" #include "update_engine/update_manager/generic_variables.h" #include "update_engine/update_manager/updater_provider.h" @@ -34,8 +33,7 @@ class RealUpdaterProvider : public UpdaterProvider { // guarantees that parts of the system state can be mocked out at any time // during testing. We further assume that, by the time Init() is called, the // system state object is fully populated and usable. - explicit RealUpdaterProvider( - chromeos_update_engine::SystemState* system_state); + RealUpdaterProvider(); // Initializes the provider and returns whether it succeeded. bool Init() { return true; } @@ -94,10 +92,11 @@ class RealUpdaterProvider : public UpdaterProvider { return var_update_restrictions_.get(); } - private: - // A pointer to the update engine's system state aggregator. - chromeos_update_engine::SystemState* system_state_; + Variable<int64_t>* var_test_update_check_interval_timeout() override { + return var_test_update_check_interval_timeout_.get(); + } + private: // Variable implementations. ConstCopyVariable<base::Time> var_updater_started_time_; std::unique_ptr<Variable<base::Time>> var_last_checked_time_; @@ -114,6 +113,7 @@ class RealUpdaterProvider : public UpdaterProvider { std::unique_ptr<Variable<unsigned int>> var_server_dictated_poll_interval_; std::unique_ptr<Variable<UpdateRequestStatus>> var_forced_update_requested_; std::unique_ptr<Variable<UpdateRestrictions>> var_update_restrictions_; + std::unique_ptr<Variable<int64_t>> var_test_update_check_interval_timeout_; DISALLOW_COPY_AND_ASSIGN(RealUpdaterProvider); }; diff --git a/update_manager/real_updater_provider_unittest.cc b/update_manager/real_updater_provider_unittest.cc index fb7a7633..4afe7fc2 100644 --- a/update_manager/real_updater_provider_unittest.cc +++ b/update_manager/real_updater_provider_unittest.cc @@ -23,16 +23,13 @@ #include <gtest/gtest.h> #include <update_engine/dbus-constants.h> -#include "update_engine/common/fake_clock.h" -#include "update_engine/common/fake_prefs.h" -#include "update_engine/fake_system_state.h" -#include "update_engine/mock_update_attempter.h" -#include "update_engine/omaha_request_params.h" +#include "update_engine/cros/fake_system_state.h" +#include "update_engine/cros/mock_update_attempter.h" +#include "update_engine/cros/omaha_request_params.h" #include "update_engine/update_manager/umtest_utils.h" using base::Time; using base::TimeDelta; -using chromeos_update_engine::FakeClock; using chromeos_update_engine::FakePrefs; using chromeos_update_engine::FakeSystemState; using chromeos_update_engine::OmahaRequestParams; @@ -100,10 +97,8 @@ namespace chromeos_update_manager { class UmRealUpdaterProviderTest : public ::testing::Test { protected: void SetUp() override { - fake_clock_ = fake_sys_state_.fake_clock(); - fake_sys_state_.set_prefs(&fake_prefs_); - provider_.reset(new RealUpdaterProvider(&fake_sys_state_)); - ASSERT_NE(nullptr, provider_.get()); + FakeSystemState::CreateInstance(); + provider_.reset(new RealUpdaterProvider()); // Check that provider initializes correctly. ASSERT_TRUE(provider_->Init()); } @@ -117,31 +112,31 @@ class UmRealUpdaterProviderTest : public ::testing::Test { const Time kCurrBootTime = (valid ? kUpdateBootTime + kDurationSinceUpdate : kUpdateBootTime - kDurationSinceUpdate); const Time kCurrWallclockTime = FixedTime(); - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetBootTimeAtUpdate(_)) .WillOnce(DoAll(SetArgPointee<0>(kUpdateBootTime), Return(true))); - fake_clock_->SetBootTime(kCurrBootTime); - fake_clock_->SetWallclockTime(kCurrWallclockTime); + FakeSystemState::Get()->fake_clock()->SetBootTime(kCurrBootTime); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(kCurrWallclockTime); return kCurrWallclockTime - kDurationSinceUpdate; } - FakeSystemState fake_sys_state_; - FakeClock* fake_clock_; // Short for fake_sys_state_.fake_clock() - FakePrefs fake_prefs_; unique_ptr<RealUpdaterProvider> provider_; }; TEST_F(UmRealUpdaterProviderTest, UpdaterStartedTimeIsWallclockTime) { - fake_clock_->SetWallclockTime(Time::FromDoubleT(123.456)); - fake_clock_->SetMonotonicTime(Time::FromDoubleT(456.123)); - // Run SetUp again to re-setup the provider under test to use these values. - SetUp(); + FakeSystemState::Get()->fake_clock()->SetWallclockTime( + Time::FromDoubleT(123.456)); + FakeSystemState::Get()->fake_clock()->SetMonotonicTime( + Time::FromDoubleT(456.123)); + // Re-initialize to re-setup the provider under test to use these values. + provider_.reset(new RealUpdaterProvider()); + ASSERT_TRUE(provider_->Init()); UmTestUtils::ExpectVariableHasValue(Time::FromDoubleT(123.456), provider_->var_updater_started_time()); } TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeOkay) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll( ActionSetUpdateEngineStatusLastCheckedTime(FixedTime().ToTimeT()), Return(true))); @@ -150,49 +145,49 @@ TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeOkay) { } TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeFailNoValue) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(Return(false)); UmTestUtils::ExpectVariableNotSet(provider_->var_last_checked_time()); } TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMin) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusProgress(0.0), Return(true))); UmTestUtils::ExpectVariableHasValue(0.0, provider_->var_progress()); } TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMid) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusProgress(0.3), Return(true))); UmTestUtils::ExpectVariableHasValue(0.3, provider_->var_progress()); } TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMax) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusProgress(1.0), Return(true))); UmTestUtils::ExpectVariableHasValue(1.0, provider_->var_progress()); } TEST_F(UmRealUpdaterProviderTest, GetProgressFailNoValue) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(Return(false)); UmTestUtils::ExpectVariableNotSet(provider_->var_progress()); } TEST_F(UmRealUpdaterProviderTest, GetProgressFailTooSmall) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusProgress(-2.0), Return(true))); UmTestUtils::ExpectVariableNotSet(provider_->var_progress()); } TEST_F(UmRealUpdaterProviderTest, GetProgressFailTooBig) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusProgress(2.0), Return(true))); UmTestUtils::ExpectVariableNotSet(provider_->var_progress()); } TEST_F(UmRealUpdaterProviderTest, GetStageOkayIdle) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll( ActionSetUpdateEngineStatusStatus(update_engine::UpdateStatus::IDLE), Return(true))); @@ -200,7 +195,7 @@ TEST_F(UmRealUpdaterProviderTest, GetStageOkayIdle) { } TEST_F(UmRealUpdaterProviderTest, GetStageOkayCheckingForUpdate) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus( update_engine::UpdateStatus::CHECKING_FOR_UPDATE), Return(true))); @@ -209,7 +204,7 @@ TEST_F(UmRealUpdaterProviderTest, GetStageOkayCheckingForUpdate) { } TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdateAvailable) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus( update_engine::UpdateStatus::UPDATE_AVAILABLE), Return(true))); @@ -218,7 +213,7 @@ TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdateAvailable) { } TEST_F(UmRealUpdaterProviderTest, GetStageOkayDownloading) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus( update_engine::UpdateStatus::DOWNLOADING), Return(true))); @@ -227,7 +222,7 @@ TEST_F(UmRealUpdaterProviderTest, GetStageOkayDownloading) { } TEST_F(UmRealUpdaterProviderTest, GetStageOkayVerifying) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus( update_engine::UpdateStatus::VERIFYING), Return(true))); @@ -236,7 +231,7 @@ TEST_F(UmRealUpdaterProviderTest, GetStageOkayVerifying) { } TEST_F(UmRealUpdaterProviderTest, GetStageOkayFinalizing) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus( update_engine::UpdateStatus::FINALIZING), Return(true))); @@ -245,7 +240,7 @@ TEST_F(UmRealUpdaterProviderTest, GetStageOkayFinalizing) { } TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdatedNeedReboot) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus( update_engine::UpdateStatus::UPDATED_NEED_REBOOT), Return(true))); @@ -254,7 +249,7 @@ TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdatedNeedReboot) { } TEST_F(UmRealUpdaterProviderTest, GetStageOkayReportingErrorEvent) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus( update_engine::UpdateStatus::REPORTING_ERROR_EVENT), Return(true))); @@ -263,7 +258,7 @@ TEST_F(UmRealUpdaterProviderTest, GetStageOkayReportingErrorEvent) { } TEST_F(UmRealUpdaterProviderTest, GetStageOkayAttemptingRollback) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusStatus( update_engine::UpdateStatus::ATTEMPTING_ROLLBACK), Return(true))); @@ -272,13 +267,13 @@ TEST_F(UmRealUpdaterProviderTest, GetStageOkayAttemptingRollback) { } TEST_F(UmRealUpdaterProviderTest, GetStageFailNoValue) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(Return(false)); UmTestUtils::ExpectVariableNotSet(provider_->var_stage()); } TEST_F(UmRealUpdaterProviderTest, GetNewVersionOkay) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce( DoAll(ActionSetUpdateEngineStatusNewVersion("1.2.0"), Return(true))); UmTestUtils::ExpectVariableHasValue(string("1.2.0"), @@ -286,13 +281,13 @@ TEST_F(UmRealUpdaterProviderTest, GetNewVersionOkay) { } TEST_F(UmRealUpdaterProviderTest, GetNewVersionFailNoValue) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(Return(false)); UmTestUtils::ExpectVariableNotSet(provider_->var_new_version()); } TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayZero) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll( ActionSetUpdateEngineStatusNewSizeBytes(static_cast<uint64_t>(0)), Return(true))); @@ -301,7 +296,7 @@ TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayZero) { } TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayArbitrary) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusNewSizeBytes( static_cast<uint64_t>(567890)), Return(true))); @@ -310,7 +305,7 @@ TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayArbitrary) { } TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayTwoGigabytes) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(DoAll(ActionSetUpdateEngineStatusNewSizeBytes( static_cast<uint64_t>(1) << 31), Return(true))); @@ -319,44 +314,44 @@ TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayTwoGigabytes) { } TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeFailNoValue) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetStatus(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetStatus(_)) .WillOnce(Return(false)); UmTestUtils::ExpectVariableNotSet(provider_->var_payload_size()); } TEST_F(UmRealUpdaterProviderTest, GetCurrChannelOkay) { const string kChannelName("foo-channel"); - OmahaRequestParams request_params(&fake_sys_state_); - request_params.Init("", "", false); + OmahaRequestParams request_params; + request_params.Init("", "", {}); request_params.set_current_channel(kChannelName); - fake_sys_state_.set_request_params(&request_params); + FakeSystemState::Get()->set_request_params(&request_params); UmTestUtils::ExpectVariableHasValue(kChannelName, provider_->var_curr_channel()); } TEST_F(UmRealUpdaterProviderTest, GetCurrChannelFailEmpty) { - OmahaRequestParams request_params(&fake_sys_state_); - request_params.Init("", "", false); + OmahaRequestParams request_params; + request_params.Init("", "", {}); request_params.set_current_channel(""); - fake_sys_state_.set_request_params(&request_params); + FakeSystemState::Get()->set_request_params(&request_params); UmTestUtils::ExpectVariableNotSet(provider_->var_curr_channel()); } TEST_F(UmRealUpdaterProviderTest, GetNewChannelOkay) { const string kChannelName("foo-channel"); - OmahaRequestParams request_params(&fake_sys_state_); - request_params.Init("", "", false); + OmahaRequestParams request_params; + request_params.Init("", "", {}); request_params.set_target_channel(kChannelName); - fake_sys_state_.set_request_params(&request_params); + FakeSystemState::Get()->set_request_params(&request_params); UmTestUtils::ExpectVariableHasValue(kChannelName, provider_->var_new_channel()); } TEST_F(UmRealUpdaterProviderTest, GetNewChannelFailEmpty) { - OmahaRequestParams request_params(&fake_sys_state_); - request_params.Init("", "", false); + OmahaRequestParams request_params; + request_params.Init("", "", {}); request_params.set_target_channel(""); - fake_sys_state_.set_request_params(&request_params); + FakeSystemState::Get()->set_request_params(&request_params); UmTestUtils::ExpectVariableNotSet(provider_->var_new_channel()); } @@ -365,22 +360,26 @@ TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledOkayPrefDoesntExist) { } TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledOkayPrefReadsFalse) { - fake_prefs_.SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, false); + FakeSystemState::Get()->fake_prefs()->SetBoolean( + chromeos_update_engine::kPrefsP2PEnabled, false); UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled()); } TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledReadWhenInitialized) { - fake_prefs_.SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, true); - SetUp(); + FakeSystemState::Get()->fake_prefs()->SetBoolean( + chromeos_update_engine::kPrefsP2PEnabled, true); + provider_.reset(new RealUpdaterProvider()); + ASSERT_TRUE(provider_->Init()); UmTestUtils::ExpectVariableHasValue(true, provider_->var_p2p_enabled()); } TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledUpdated) { - fake_prefs_.SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, false); + auto* fake_prefs = FakeSystemState::Get()->fake_prefs(); + fake_prefs->SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, false); UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled()); - fake_prefs_.SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, true); + fake_prefs->SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, true); UmTestUtils::ExpectVariableHasValue(true, provider_->var_p2p_enabled()); - fake_prefs_.Delete(chromeos_update_engine::kPrefsP2PEnabled); + fake_prefs->Delete(chromeos_update_engine::kPrefsP2PEnabled); UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled()); } @@ -389,7 +388,7 @@ TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledOkayPrefDoesntExist) { } TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledOkayPrefReadsTrue) { - fake_prefs_.SetBoolean( + FakeSystemState::Get()->fake_prefs()->SetBoolean( chromeos_update_engine::kPrefsUpdateOverCellularPermission, true); UmTestUtils::ExpectVariableHasValue(true, provider_->var_cellular_enabled()); } @@ -401,7 +400,8 @@ TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeOkay) { } TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeFailNoValue) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetBootTimeAtUpdate(_)) + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), + GetBootTimeAtUpdate(_)) .WillOnce(Return(false)); UmTestUtils::ExpectVariableNotSet(provider_->var_update_completed_time()); } @@ -413,7 +413,7 @@ TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeFailInvalidValue) { TEST_F(UmRealUpdaterProviderTest, GetConsecutiveFailedUpdateChecks) { const unsigned int kNumFailedChecks = 3; - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), consecutive_failed_update_checks()) .WillRepeatedly(Return(kNumFailedChecks)); UmTestUtils::ExpectVariableHasValue( @@ -422,7 +422,7 @@ TEST_F(UmRealUpdaterProviderTest, GetConsecutiveFailedUpdateChecks) { TEST_F(UmRealUpdaterProviderTest, GetServerDictatedPollInterval) { const unsigned int kPollInterval = 2 * 60 * 60; // Two hours. - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), server_dictated_poll_interval()) .WillRepeatedly(Return(kPollInterval)); UmTestUtils::ExpectVariableHasValue( @@ -430,7 +430,7 @@ TEST_F(UmRealUpdaterProviderTest, GetServerDictatedPollInterval) { } TEST_F(UmRealUpdaterProviderTest, GetUpdateRestrictions) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetCurrentUpdateAttemptFlags()) .WillRepeatedly(Return(UpdateAttemptFlags::kFlagRestrictDownload | UpdateAttemptFlags::kFlagNonInteractive)); @@ -439,10 +439,36 @@ TEST_F(UmRealUpdaterProviderTest, GetUpdateRestrictions) { } TEST_F(UmRealUpdaterProviderTest, GetUpdateRestrictionsNone) { - EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), + EXPECT_CALL(*FakeSystemState::Get()->mock_update_attempter(), GetCurrentUpdateAttemptFlags()) .WillRepeatedly(Return(UpdateAttemptFlags::kNone)); UmTestUtils::ExpectVariableHasValue(UpdateRestrictions::kNone, provider_->var_update_restrictions()); } + +TEST_F(UmRealUpdaterProviderTest, TestUpdateCheckIntervalTimeout) { + UmTestUtils::ExpectVariableNotSet( + provider_->var_test_update_check_interval_timeout()); + auto* fake_prefs = FakeSystemState::Get()->fake_prefs(); + fake_prefs->SetInt64( + chromeos_update_engine::kPrefsTestUpdateCheckIntervalTimeout, 1); + UmTestUtils::ExpectVariableHasValue( + static_cast<int64_t>(1), + provider_->var_test_update_check_interval_timeout()); + + // Make sure the value does not exceed a threshold of 10 minutes. + fake_prefs->SetInt64( + chromeos_update_engine::kPrefsTestUpdateCheckIntervalTimeout, 11 * 60); + // The next 5 reads should return valid values. + for (int i = 0; i < 5; ++i) + UmTestUtils::ExpectVariableHasValue( + static_cast<int64_t>(10 * 60), + provider_->var_test_update_check_interval_timeout()); + + // Just to make sure it is not cached anywhere and deleted. The variable is + // allowd to be read 6 times. + UmTestUtils::ExpectVariableNotSet( + provider_->var_test_update_check_interval_timeout()); +} + } // namespace chromeos_update_manager diff --git a/update_manager/rollback_prefs.h b/update_manager/rollback_prefs.h index 11d09d67..6cbc447d 100644 --- a/update_manager/rollback_prefs.h +++ b/update_manager/rollback_prefs.h @@ -31,9 +31,21 @@ enum class RollbackToTargetVersion { kDisabled = 1, kRollbackAndPowerwash = 2, kRollbackAndRestoreIfPossible = 3, - kRollbackOnlyIfRestorePossible = 4, // This value must be the last entry. - kMaxValue = 5 + kMaxValue = 4 +}; + +// Whether the device should do rollback and powerwash on channel downgrade. +// Matches chrome_device_policy.proto's +// |AutoUpdateSettingsProto::ChannelDowngradeBehavior|. +enum class ChannelDowngradeBehavior { + kUnspecified = 0, + kWaitForVersionToCatchUp = 1, + kRollback = 2, + kAllowUserToConfigure = 3, + // These values must be kept up to date. + kFirstValue = kUnspecified, + kLastValue = kAllowUserToConfigure }; } // namespace chromeos_update_manager diff --git a/update_manager/shill_provider.h b/update_manager/shill_provider.h index c7bb2e22..ebe7a3ae 100644 --- a/update_manager/shill_provider.h +++ b/update_manager/shill_provider.h @@ -19,7 +19,7 @@ #include <base/time/time.h> -#include "update_engine/connection_utils.h" +#include "update_engine/common/connection_utils.h" #include "update_engine/update_manager/provider.h" #include "update_engine/update_manager/variable.h" diff --git a/update_manager/staging_utils.cc b/update_manager/staging_utils.cc index 4835ab22..a9929751 100644 --- a/update_manager/staging_utils.cc +++ b/update_manager/staging_utils.cc @@ -26,12 +26,10 @@ #include "update_engine/common/constants.h" #include "update_engine/common/hardware_interface.h" -#include "update_engine/common/prefs_interface.h" -#include "update_engine/system_state.h" +#include "update_engine/common/system_state.h" using base::TimeDelta; using chromeos_update_engine::kPrefsWallClockStagingWaitPeriod; -using chromeos_update_engine::PrefsInterface; using chromeos_update_engine::SystemState; using policy::DevicePolicy; @@ -99,7 +97,6 @@ int CalculateWaitTimeInDaysFromSchedule( } StagingCase CalculateStagingCase(const DevicePolicy* device_policy, - PrefsInterface* prefs, TimeDelta* staging_wait_time, StagingSchedule* staging_schedule) { // Check that the schedule in the device policy is correct. @@ -129,7 +126,8 @@ StagingCase CalculateStagingCase(const DevicePolicy* device_policy, int64_t wait_period_in_days; // There exists a persisted value that is valid. That is, it's smaller than // the maximum amount of days of staging set by the user. - if (prefs->GetInt64(kPrefsWallClockStagingWaitPeriod, &wait_period_in_days) && + if (SystemState::Get()->prefs()->GetInt64(kPrefsWallClockStagingWaitPeriod, + &wait_period_in_days) && wait_period_in_days > 0 && wait_period_in_days <= max_days) { *staging_wait_time = TimeDelta::FromDays(wait_period_in_days); return StagingCase::kSetStagingFromPref; diff --git a/update_manager/staging_utils.h b/update_manager/staging_utils.h index e91bfebc..0de1dfdc 100644 --- a/update_manager/staging_utils.h +++ b/update_manager/staging_utils.h @@ -62,7 +62,6 @@ int GetStagingSchedule(const policy::DevicePolicy* device_policy, // contain the previous staging schedule, if there is a new schedule found, its // value will be replaced with the new one. StagingCase CalculateStagingCase(const policy::DevicePolicy* device_policy, - chromeos_update_engine::PrefsInterface* prefs, base::TimeDelta* staging_wait_time, StagingSchedule* staging_schedule); diff --git a/update_manager/staging_utils_unittest.cc b/update_manager/staging_utils_unittest.cc index 8d75acd6..126617f1 100644 --- a/update_manager/staging_utils_unittest.cc +++ b/update_manager/staging_utils_unittest.cc @@ -24,10 +24,10 @@ #include <policy/mock_device_policy.h> #include "update_engine/common/constants.h" -#include "update_engine/common/fake_prefs.h" +#include "update_engine/cros/fake_system_state.h" using base::TimeDelta; -using chromeos_update_engine::FakePrefs; +using chromeos_update_engine::FakeSystemState; using chromeos_update_engine::kPrefsWallClockStagingWaitPeriod; using testing::_; using testing::DoAll; @@ -44,6 +44,7 @@ const StagingSchedule valid_schedule = {{2, 0}, {7, 50}, {9, 80}, {14, 100}}; class StagingUtilsScheduleTest : public testing::Test { protected: void SetUp() override { + FakeSystemState::CreateInstance(); test_wait_time_ = TimeDelta(); test_staging_schedule_ = StagingSchedule(); } @@ -55,14 +56,13 @@ class StagingUtilsScheduleTest : public testing::Test { } void SetPersistedStagingVal(int64_t wait_time) { - EXPECT_TRUE( - fake_prefs_.SetInt64(kPrefsWallClockStagingWaitPeriod, wait_time)); + EXPECT_TRUE(FakeSystemState::Get()->fake_prefs()->SetInt64( + kPrefsWallClockStagingWaitPeriod, wait_time)); } void TestStagingCase(const StagingCase& expected) { EXPECT_EQ(expected, CalculateStagingCase(&device_policy_, - &fake_prefs_, &test_wait_time_, &test_staging_schedule_)); } @@ -75,7 +75,6 @@ class StagingUtilsScheduleTest : public testing::Test { policy::MockDevicePolicy device_policy_; TimeDelta test_wait_time_; StagingSchedule test_staging_schedule_; - FakePrefs fake_prefs_; }; // Last element should be 100, if not return false. diff --git a/update_manager/state_factory.cc b/update_manager/state_factory.cc index 78cec6a0..0ab4f7be 100644 --- a/update_manager/state_factory.cc +++ b/update_manager/state_factory.cc @@ -23,34 +23,31 @@ #include <session_manager/dbus-proxies.h> #endif // USE_DBUS -#include "update_engine/common/clock_interface.h" #if USE_DBUS -#include "update_engine/dbus_connection.h" +#include "update_engine/cros/dbus_connection.h" #endif // USE_DBUS +#include "update_engine/common/system_state.h" +#include "update_engine/cros/shill_proxy.h" #include "update_engine/update_manager/fake_shill_provider.h" #include "update_engine/update_manager/real_config_provider.h" #include "update_engine/update_manager/real_device_policy_provider.h" #include "update_engine/update_manager/real_random_provider.h" +#include "update_engine/update_manager/real_shill_provider.h" #include "update_engine/update_manager/real_state.h" #include "update_engine/update_manager/real_system_provider.h" #include "update_engine/update_manager/real_time_provider.h" #include "update_engine/update_manager/real_updater_provider.h" -#if USE_SHILL -#include "update_engine/shill_proxy.h" -#include "update_engine/update_manager/real_shill_provider.h" -#endif // USE_SHILL +using chromeos_update_engine::SystemState; using std::unique_ptr; namespace chromeos_update_manager { State* DefaultStateFactory( policy::PolicyProvider* policy_provider, - org::chromium::KioskAppServiceInterfaceProxyInterface* kiosk_app_proxy, - chromeos_update_engine::SystemState* system_state) { - chromeos_update_engine::ClockInterface* const clock = system_state->clock(); + org::chromium::KioskAppServiceInterfaceProxyInterface* kiosk_app_proxy) { unique_ptr<RealConfigProvider> config_provider( - new RealConfigProvider(system_state->hardware())); + new RealConfigProvider(SystemState::Get()->hardware())); #if USE_DBUS scoped_refptr<dbus::Bus> bus = chromeos_update_engine::DBusConnection::Get()->GetDBus(); @@ -62,25 +59,18 @@ State* DefaultStateFactory( unique_ptr<RealDevicePolicyProvider> device_policy_provider( new RealDevicePolicyProvider(policy_provider)); #endif // USE_DBUS -#if USE_SHILL unique_ptr<RealShillProvider> shill_provider( - new RealShillProvider(new chromeos_update_engine::ShillProxy(), clock)); -#else - unique_ptr<FakeShillProvider> shill_provider(new FakeShillProvider()); -#endif // USE_SHILL + new RealShillProvider(new chromeos_update_engine::ShillProxy())); unique_ptr<RealRandomProvider> random_provider(new RealRandomProvider()); - unique_ptr<RealSystemProvider> system_provider(new RealSystemProvider( - system_state->hardware(), system_state->boot_control(), kiosk_app_proxy)); + unique_ptr<RealSystemProvider> system_provider( + new RealSystemProvider(kiosk_app_proxy)); - unique_ptr<RealTimeProvider> time_provider(new RealTimeProvider(clock)); - unique_ptr<RealUpdaterProvider> updater_provider( - new RealUpdaterProvider(system_state)); + unique_ptr<RealTimeProvider> time_provider(new RealTimeProvider()); + unique_ptr<RealUpdaterProvider> updater_provider(new RealUpdaterProvider()); if (!(config_provider->Init() && device_policy_provider->Init() && random_provider->Init() && -#if USE_SHILL shill_provider->Init() && -#endif // USE_SHILL system_provider->Init() && time_provider->Init() && updater_provider->Init())) { LOG(ERROR) << "Error initializing providers"; diff --git a/update_manager/state_factory.h b/update_manager/state_factory.h index 1c1c1d95..c53bb9cb 100644 --- a/update_manager/state_factory.h +++ b/update_manager/state_factory.h @@ -17,7 +17,6 @@ #ifndef UPDATE_ENGINE_UPDATE_MANAGER_STATE_FACTORY_H_ #define UPDATE_ENGINE_UPDATE_MANAGER_STATE_FACTORY_H_ -#include "update_engine/system_state.h" #include "update_engine/update_manager/state.h" namespace org { @@ -35,8 +34,7 @@ namespace chromeos_update_manager { // to initialize. State* DefaultStateFactory( policy::PolicyProvider* policy_provider, - org::chromium::KioskAppServiceInterfaceProxyInterface* kiosk_app_proxy, - chromeos_update_engine::SystemState* system_state); + org::chromium::KioskAppServiceInterfaceProxyInterface* kiosk_app_proxy); } // namespace chromeos_update_manager diff --git a/update_manager/system_provider.h b/update_manager/system_provider.h index 13e188bb..8eb14e3b 100644 --- a/update_manager/system_provider.h +++ b/update_manager/system_provider.h @@ -17,6 +17,10 @@ #ifndef UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_ #define UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_ +#include <string> + +#include <base/version.h> + #include "update_engine/update_manager/provider.h" #include "update_engine/update_manager/variable.h" @@ -46,6 +50,9 @@ class SystemProvider : public Provider { // with zero delay kiosk app if any. virtual Variable<std::string>* var_kiosk_required_platform_version() = 0; + // Chrome OS version number as provided by |ImagePropeties|. + virtual Variable<base::Version>* var_chromeos_version() = 0; + protected: SystemProvider() {} diff --git a/update_manager/update_manager-inl.h b/update_manager/update_manager-inl.h index e9dee3f2..045ecffa 100644 --- a/update_manager/update_manager-inl.h +++ b/update_manager/update_manager-inl.h @@ -49,7 +49,6 @@ EvalStatus UpdateManager::EvaluatePolicy( ec->ResetEvaluation(); const std::string policy_name = policy_->PolicyRequestName(policy_method); - LOG(INFO) << policy_name << ": START"; // First try calling the actual policy. std::string error; @@ -71,14 +70,12 @@ EvalStatus UpdateManager::EvaluatePolicy( } } - LOG(INFO) << policy_name << ": END"; - return status; } template <typename R, typename... Args> void UpdateManager::OnPolicyReadyToEvaluate( - scoped_refptr<EvaluationContext> ec, + std::shared_ptr<EvaluationContext> ec, base::Callback<void(EvalStatus status, const R& result)> callback, EvalStatus (Policy::*policy_method)( EvaluationContext*, State*, std::string*, R*, Args...) const, @@ -119,8 +116,7 @@ EvalStatus UpdateManager::PolicyRequest( EvaluationContext*, State*, std::string*, R*, ExpectedArgs...) const, R* result, ActualArgs... args) { - scoped_refptr<EvaluationContext> ec( - new EvaluationContext(clock_, evaluation_timeout_)); + auto ec = std::make_shared<EvaluationContext>(evaluation_timeout_); // A PolicyRequest always consists on a single evaluation on a new // EvaluationContext. // IMPORTANT: To ensure that ActualArgs can be converted to ExpectedArgs, we @@ -141,15 +137,14 @@ void UpdateManager::AsyncPolicyRequest( EvalStatus (Policy::*policy_method)( EvaluationContext*, State*, std::string*, R*, ExpectedArgs...) const, ActualArgs... args) { - scoped_refptr<EvaluationContext> ec = new EvaluationContext( - clock_, + auto ec = std::make_shared<EvaluationContext>( evaluation_timeout_, expiration_timeout_, std::unique_ptr<base::Callback<void(EvaluationContext*)>>( new base::Callback<void(EvaluationContext*)>( base::Bind(&UpdateManager::UnregisterEvalContext, weak_ptr_factory_.GetWeakPtr())))); - if (!ec_repo_.insert(ec.get()).second) { + if (!ec_repo_.insert(ec).second) { LOG(ERROR) << "Failed to register evaluation context; this is a bug."; } diff --git a/update_manager/update_manager.cc b/update_manager/update_manager.cc index 5dfc09cf..dbb6b336 100644 --- a/update_manager/update_manager.cc +++ b/update_manager/update_manager.cc @@ -15,32 +15,18 @@ // #include "update_engine/update_manager/update_manager.h" - -#ifdef __ANDROID__ -#include "update_engine/update_manager/android_things_policy.h" -#else -#include "update_engine/update_manager/chromeos_policy.h" -#endif // __ANDROID__ #include "update_engine/update_manager/state.h" namespace chromeos_update_manager { -UpdateManager::UpdateManager(chromeos_update_engine::ClockInterface* clock, - base::TimeDelta evaluation_timeout, +UpdateManager::UpdateManager(base::TimeDelta evaluation_timeout, base::TimeDelta expiration_timeout, State* state) - : default_policy_(clock), + : policy_(GetSystemPolicy()), state_(state), - clock_(clock), evaluation_timeout_(evaluation_timeout), expiration_timeout_(expiration_timeout), - weak_ptr_factory_(this) { -#ifdef __ANDROID__ - policy_.reset(new AndroidThingsPolicy()); -#else - policy_.reset(new ChromeOSPolicy()); -#endif // __ANDROID__ -} + weak_ptr_factory_(this) {} UpdateManager::~UpdateManager() { // Remove pending main loop events associated with any of the outstanding @@ -50,8 +36,19 @@ UpdateManager::~UpdateManager() { ec->RemoveObserversAndTimeout(); } +void UpdateManager::AsyncPolicyRequestUpdateCheckAllowed( + base::Callback<void(EvalStatus, const UpdateCheckParams& result)> callback, + EvalStatus (Policy::*policy_method)( + EvaluationContext*, State*, std::string*, UpdateCheckParams*) const) { + AsyncPolicyRequest(callback, policy_method); +} + void UpdateManager::UnregisterEvalContext(EvaluationContext* ec) { - if (!ec_repo_.erase(ec)) { + // Since |ec_repo_|'s compare function is based on the value of the raw + // pointer |ec|, we can just create a |shared_ptr| here and pass it along to + // be erased. + if (!ec_repo_.erase( + std::shared_ptr<EvaluationContext>(ec, [](EvaluationContext*) {}))) { LOG(ERROR) << "Unregistering an unknown evaluation context, this is a bug."; } } diff --git a/update_manager/update_manager.h b/update_manager/update_manager.h index b0fd97fa..e266b574 100644 --- a/update_manager/update_manager.h +++ b/update_manager/update_manager.h @@ -22,10 +22,9 @@ #include <string> #include <base/callback.h> -#include <base/memory/ref_counted.h> #include <base/time/time.h> -#include "update_engine/common/clock_interface.h" +#include "update_engine/common/system_state.h" #include "update_engine/update_manager/default_policy.h" #include "update_engine/update_manager/evaluation_context.h" #include "update_engine/update_manager/policy.h" @@ -33,22 +32,31 @@ namespace chromeos_update_manager { -// Comparator for scoped_refptr objects. -template <typename T> -struct ScopedRefPtrLess { - bool operator()(const scoped_refptr<T>& first, - const scoped_refptr<T>& second) const { - return first.get() < second.get(); - } +// Please do not move this class into a new file for simplicity. +// This pure virtual class is purely created for purpose of testing. The reason +// was that |UpdateManager|'s member functions are templatized, which does not +// play nicely when testing (mocking + faking). Whenever a specialized member of +// |UpdateManager| must be tested, please add a specialized template member +// function within this class for testing. +class SpecializedPolicyRequestInterface { + public: + virtual ~SpecializedPolicyRequestInterface() = default; + + virtual void AsyncPolicyRequestUpdateCheckAllowed( + base::Callback<void(EvalStatus, const UpdateCheckParams& result)> + callback, + EvalStatus (Policy::*policy_method)(EvaluationContext*, + State*, + std::string*, + UpdateCheckParams*) const) = 0; }; // The main Update Manager singleton class. -class UpdateManager { +class UpdateManager : public SpecializedPolicyRequestInterface { public: // Creates the UpdateManager instance, assuming ownership on the provided // |state|. - UpdateManager(chromeos_update_engine::ClockInterface* clock, - base::TimeDelta evaluation_timeout, + UpdateManager(base::TimeDelta evaluation_timeout, base::TimeDelta expiration_timeout, State* state); @@ -91,6 +99,14 @@ class UpdateManager { EvaluationContext*, State*, std::string*, R*, ExpectedArgs...) const, ActualArgs... args); + void AsyncPolicyRequestUpdateCheckAllowed( + base::Callback<void(EvalStatus, const UpdateCheckParams& result)> + callback, + EvalStatus (Policy::*policy_method)(EvaluationContext*, + State*, + std::string*, + UpdateCheckParams*) const) override; + protected: // The UpdateManager receives ownership of the passed Policy instance. void set_policy(const Policy* policy) { policy_.reset(policy); } @@ -125,7 +141,7 @@ class UpdateManager { // the evaluation will be re-scheduled to be called later. template <typename R, typename... Args> void OnPolicyReadyToEvaluate( - scoped_refptr<EvaluationContext> ec, + std::shared_ptr<EvaluationContext> ec, base::Callback<void(EvalStatus status, const R& result)> callback, EvalStatus (Policy::*policy_method)( EvaluationContext*, State*, std::string*, R*, Args...) const, @@ -145,9 +161,6 @@ class UpdateManager { // State Providers. std::unique_ptr<State> state_; - // Pointer to the mockable clock interface; - chromeos_update_engine::ClockInterface* clock_; - // Timeout for a policy evaluation. const base::TimeDelta evaluation_timeout_; @@ -159,9 +172,7 @@ class UpdateManager { // destructed; alternatively, when the UpdateManager instance is destroyed, it // will remove all pending events associated with all outstanding contexts // (which should, in turn, trigger their destruction). - std::set<scoped_refptr<EvaluationContext>, - ScopedRefPtrLess<EvaluationContext>> - ec_repo_; + std::set<std::shared_ptr<EvaluationContext>> ec_repo_; base::WeakPtrFactory<UpdateManager> weak_ptr_factory_; diff --git a/update_manager/update_manager_unittest.cc b/update_manager/update_manager_unittest.cc index f1a8d173..a02d7ef5 100644 --- a/update_manager/update_manager_unittest.cc +++ b/update_manager/update_manager_unittest.cc @@ -34,7 +34,7 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> -#include "update_engine/common/fake_clock.h" +#include "update_engine/cros/fake_system_state.h" #include "update_engine/update_manager/default_policy.h" #include "update_engine/update_manager/fake_state.h" #include "update_engine/update_manager/mock_policy.h" @@ -48,6 +48,7 @@ using brillo::MessageLoop; using brillo::MessageLoopRunMaxIterations; using chromeos_update_engine::ErrorCode; using chromeos_update_engine::FakeClock; +using chromeos_update_engine::FakeSystemState; using std::pair; using std::string; using std::tuple; @@ -80,11 +81,10 @@ class UmUpdateManagerTest : public ::testing::Test { protected: void SetUp() override { loop_.SetAsCurrent(); + FakeSystemState::CreateInstance(); fake_state_ = new FakeState(); - umut_.reset(new UpdateManager(&fake_clock_, - TimeDelta::FromSeconds(5), - TimeDelta::FromSeconds(1), - fake_state_)); + umut_.reset(new UpdateManager( + TimeDelta::FromSeconds(5), TimeDelta::FromSeconds(1), fake_state_)); } void TearDown() override { EXPECT_FALSE(loop_.PendingTasks()); } @@ -92,7 +92,6 @@ class UmUpdateManagerTest : public ::testing::Test { base::SimpleTestClock test_clock_; brillo::FakeMessageLoop loop_{&test_clock_}; FakeState* fake_state_; // Owned by the umut_. - FakeClock fake_clock_; unique_ptr<UpdateManager> umut_; }; @@ -291,13 +290,14 @@ TEST_F(UmUpdateManagerTest, AsyncPolicyRequestTimeoutDoesNotFire) { } TEST_F(UmUpdateManagerTest, AsyncPolicyRequestTimesOut) { + auto* fake_clock = FakeSystemState::Get()->fake_clock(); // Set up an async policy call to exceed its expiration timeout, make sure // that the default policy was not used (no callback) and that evaluation is // reattempted. int num_called = 0; umut_->set_policy(new DelayPolicy( 0, - fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(3), + fake_clock->GetWallclockTime() + TimeDelta::FromSeconds(3), &num_called)); vector<pair<EvalStatus, UpdateCheckParams>> calls; @@ -314,7 +314,7 @@ TEST_F(UmUpdateManagerTest, AsyncPolicyRequestTimesOut) { // ensure that reevaluation occurred but callback was not invoked (i.e. // default policy was not consulted). test_clock_.Advance(TimeDelta::FromSeconds(2)); - fake_clock_.SetWallclockTime(fake_clock_.GetWallclockTime() + + fake_clock->SetWallclockTime(fake_clock->GetWallclockTime() + TimeDelta::FromSeconds(2)); MessageLoopRunMaxIterations(MessageLoop::current(), 10); EXPECT_EQ(2, num_called); @@ -322,7 +322,7 @@ TEST_F(UmUpdateManagerTest, AsyncPolicyRequestTimesOut) { // Wait for reevaluation due to delay to happen, ensure that it occurs and // that the callback is invoked. test_clock_.Advance(TimeDelta::FromSeconds(2)); - fake_clock_.SetWallclockTime(fake_clock_.GetWallclockTime() + + fake_clock->SetWallclockTime(fake_clock->GetWallclockTime() + TimeDelta::FromSeconds(2)); MessageLoopRunMaxIterations(MessageLoop::current(), 10); EXPECT_EQ(3, num_called); diff --git a/update_manager/update_time_restrictions_monitor.cc b/update_manager/update_time_restrictions_monitor.cc new file mode 100644 index 00000000..00e6ec3e --- /dev/null +++ b/update_manager/update_time_restrictions_monitor.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/update_manager/update_time_restrictions_monitor.h" + +#include <base/bind.h> +#include <base/time/time.h> + +#include "update_engine/common/system_state.h" + +using base::TimeDelta; +using brillo::MessageLoop; +using chromeos_update_engine::SystemState; + +namespace chromeos_update_manager { + +namespace { + +const WeeklyTimeInterval* FindNextNearestInterval( + const WeeklyTimeIntervalVector& intervals, const WeeklyTime& now) { + const WeeklyTimeInterval* result_interval = nullptr; + // As we are dealing with weekly time here, the maximum duration can be one + // week. + TimeDelta duration_till_next_interval = TimeDelta::FromDays(7); + for (const auto& interval : intervals) { + if (interval.InRange(now)) { + return &interval; + } + const TimeDelta current_duration = now.GetDurationTo(interval.start()); + if (current_duration < duration_till_next_interval) { + result_interval = &interval; + duration_till_next_interval = current_duration; + } + } + return result_interval; +} + +WeeklyTime Now() { + return WeeklyTime::FromTime(SystemState::Get()->clock()->GetWallclockTime()); +} + +} // namespace + +UpdateTimeRestrictionsMonitor::UpdateTimeRestrictionsMonitor( + DevicePolicyProvider* device_policy_provider, Delegate* delegate) + : evaluation_context_(/* evaluation_timeout = */ TimeDelta::Max(), + /* expiration_timeout = */ TimeDelta::Max(), + /* unregister_cb = */ {}), + device_policy_provider_(device_policy_provider), + delegate_(delegate), + weak_ptr_factory_(this) { + if (device_policy_provider_ != nullptr && delegate_ != nullptr) + StartMonitoring(); +} + +UpdateTimeRestrictionsMonitor::~UpdateTimeRestrictionsMonitor() { + StopMonitoring(); +} + +void UpdateTimeRestrictionsMonitor::StartMonitoring() { + DCHECK(device_policy_provider_); + const WeeklyTimeIntervalVector* new_intervals = evaluation_context_.GetValue( + device_policy_provider_->var_disallowed_time_intervals()); + if (new_intervals && !new_intervals->empty()) + WaitForRestrictedIntervalStarts(*new_intervals); + + const bool is_registered = evaluation_context_.RunOnValueChangeOrTimeout( + base::Bind(&UpdateTimeRestrictionsMonitor::OnIntervalsChanged, + base::Unretained(this))); + DCHECK(is_registered); +} + +void UpdateTimeRestrictionsMonitor::WaitForRestrictedIntervalStarts( + const WeeklyTimeIntervalVector& restricted_time_intervals) { + DCHECK(!restricted_time_intervals.empty()); + + const WeeklyTimeInterval* current_interval = + FindNextNearestInterval(restricted_time_intervals, Now()); + if (current_interval == nullptr) { + LOG(WARNING) << "Could not find next nearest restricted interval."; + return; + } + + // If |current_interval| happens right now, set delay to zero. + const TimeDelta duration_till_start = + current_interval->InRange(Now()) + ? TimeDelta::FromMicroseconds(0) + : Now().GetDurationTo(current_interval->start()); + LOG(INFO) << "Found restricted interval starting at " + << (SystemState::Get()->clock()->GetWallclockTime() + + duration_till_start); + + timeout_event_ = MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&UpdateTimeRestrictionsMonitor::HandleRestrictedIntervalStarts, + weak_ptr_factory_.GetWeakPtr()), + duration_till_start); +} + +void UpdateTimeRestrictionsMonitor::HandleRestrictedIntervalStarts() { + timeout_event_ = MessageLoop::kTaskIdNull; + if (delegate_) + delegate_->OnRestrictedIntervalStarts(); +} + +void UpdateTimeRestrictionsMonitor::StopMonitoring() { + MessageLoop::current()->CancelTask(timeout_event_); + timeout_event_ = MessageLoop::kTaskIdNull; +} + +void UpdateTimeRestrictionsMonitor::OnIntervalsChanged() { + DCHECK(!evaluation_context_.is_expired()); + + StopMonitoring(); + evaluation_context_.ResetEvaluation(); + StartMonitoring(); +} + +} // namespace chromeos_update_manager diff --git a/update_manager/update_time_restrictions_monitor.h b/update_manager/update_time_restrictions_monitor.h new file mode 100644 index 00000000..034ac872 --- /dev/null +++ b/update_manager/update_time_restrictions_monitor.h @@ -0,0 +1,105 @@ +// +// 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_UPDATE_MANAGER_UPDATE_TIME_RESTRICTIONS_MONITOR_H_ +#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_TIME_RESTRICTIONS_MONITOR_H_ + +#include <memory> + +#include <base/memory/weak_ptr.h> +#include <brillo/message_loops/message_loop.h> + +#include "update_engine/update_manager/device_policy_provider.h" +#include "update_engine/update_manager/evaluation_context.h" +#include "update_engine/update_manager/weekly_time.h" + +namespace chromeos_update_manager { + +// Represents a monitor tracking start of restricted time intervals during which +// update download is not allowed. It reads |var_disallowed_time_intervals|, +// chooses the next interval according to current time, awaits its start and +// notifies the delegate. If the chosen interval is already happening, the +// monitor notifies immediately. The monitor will never notify the delegate +// while the current list of restricted intervals is empty. +// +// The monitor detects changes in the restricted intervals and handles the +// change with following cases: +// 1. No restricted time intervals or none of the intervals is in progress -> no +// new restricted intervals or none of the new intervals matches the current +// time. +// The monitor starts tracking the next interval from the new ones, if any. +// 2. No restricted time intervals or none of the intervals is in progress -> +// there is a new interval matching current time. +// The monitor shall pick this new interval and notify the delegate +// immediately about the start of the restricted interval. +class UpdateTimeRestrictionsMonitor { + public: + // Interface to handle start of a restricted time interval. + class Delegate { + public: + virtual ~Delegate() = default; + + virtual void OnRestrictedIntervalStarts() = 0; + }; + + // Creates an instance and starts monitoring the next nearest restricted time + // interval if present. If no intervals are available yet the monitor will be + // idle until intervals list changes. + UpdateTimeRestrictionsMonitor(DevicePolicyProvider* device_policy_provider, + Delegate* delegate); + + UpdateTimeRestrictionsMonitor(const UpdateTimeRestrictionsMonitor&) = delete; + UpdateTimeRestrictionsMonitor& operator=( + const UpdateTimeRestrictionsMonitor&) = delete; + + ~UpdateTimeRestrictionsMonitor(); + + bool IsMonitoringInterval() { + return timeout_event_ != brillo::MessageLoop::kTaskIdNull; + } + + private: + // Starts monitoring the start of nearest restricted time interval if present + // and any change in restricted time intervals from policy. + void StartMonitoring(); + void WaitForRestrictedIntervalStarts( + const WeeklyTimeIntervalVector& restricted_time_intervals); + + // Called when current time lies within a restricted interval. + void HandleRestrictedIntervalStarts(); + + // Stop monotoring any restricted intervals. + void StopMonitoring(); + + // Called upon change of restricted intervals. + void OnIntervalsChanged(); + + // To access restricted time intervals from |device_policy_provider_|. + EvaluationContext evaluation_context_; + + DevicePolicyProvider* const device_policy_provider_; + Delegate* const delegate_; + + // The TaskId returned by the message loop identifying the timeout callback. + // Used for cancelling the timeout callback. + brillo::MessageLoop::TaskId timeout_event_{brillo::MessageLoop::kTaskIdNull}; + + base::WeakPtrFactory<UpdateTimeRestrictionsMonitor> weak_ptr_factory_; +}; + +} // namespace chromeos_update_manager + +#endif // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_TIME_RESTRICTIONS_MONITOR_H_ diff --git a/update_manager/update_time_restrictions_monitor_unittest.cc b/update_manager/update_time_restrictions_monitor_unittest.cc new file mode 100644 index 00000000..2e474e25 --- /dev/null +++ b/update_manager/update_time_restrictions_monitor_unittest.cc @@ -0,0 +1,279 @@ +// +// 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 <memory> + +#include <base/optional.h> +#include <base/time/time.h> +#include <base/test/simple_test_clock.h> +#include <brillo/message_loops/fake_message_loop.h> +#include <brillo/message_loops/message_loop_utils.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "update_engine/cros/fake_system_state.h" +#include "update_engine/update_manager/fake_state.h" +#include "update_engine/update_manager/update_time_restrictions_monitor.h" + +using brillo::FakeMessageLoop; +using brillo::MessageLoop; +using brillo::MessageLoopRunMaxIterations; +using chromeos_update_engine::FakeSystemState; + +namespace chromeos_update_manager { + +namespace { + +constexpr base::TimeDelta kDurationOffset = base::TimeDelta::FromMinutes(1); +constexpr base::TimeDelta kHourDuration = base::TimeDelta::FromHours(1); +constexpr base::TimeDelta kMinuteDuration = base::TimeDelta::FromMinutes(1); +// Initial time: Monday, May 4th 2020 8:13 AM before interval. +constexpr base::Time::Exploded kInitialTimeBeforeInterval{ + 2020, 5, 0, 4, 10, 13, 0, 0}; +// Initial time: Monday, May 4th 2020 10:20 AM within interval. +constexpr base::Time::Exploded kInitialTimeWithinInterval{ + 2020, 5, 0, 4, 10, 20, 0, 0}; +const int current_restricted_interval_index = 0; + +const WeeklyTimeIntervalVector kTestOneDisallowedTimeIntervals{ + // Monday 8:15 AM to Monday 9:30 PM. + WeeklyTimeInterval(WeeklyTime(1, kHourDuration * 8 + kMinuteDuration * 15), + WeeklyTime(1, kHourDuration * 9 + kMinuteDuration * 30)), +}; + +const WeeklyTimeIntervalVector kTestTwoDisallowedTimeIntervals{ + // Monday 10:15 AM to Monday 3:30 PM. + WeeklyTimeInterval( + WeeklyTime(1, kHourDuration * 10 + kMinuteDuration * 15), + WeeklyTime(1, kHourDuration * 15 + kMinuteDuration * 30)), + // Wednesday 8:30 PM to Thursday 8:40 AM. + WeeklyTimeInterval(WeeklyTime(3, kHourDuration * 20 + kMinuteDuration * 30), + WeeklyTime(4, kHourDuration * 8 + kMinuteDuration * 40)), +}; + +} // namespace + +class MockUpdateTimeRestrictionsMonitorDelegate + : public UpdateTimeRestrictionsMonitor::Delegate { + public: + virtual ~MockUpdateTimeRestrictionsMonitorDelegate() = default; + + MOCK_METHOD0(OnRestrictedIntervalStarts, void()); +}; + +class UmUpdateTimeRestrictionsMonitorTest : public ::testing::Test { + protected: + UmUpdateTimeRestrictionsMonitorTest() { + fake_loop_.SetAsCurrent(); + FakeSystemState::CreateInstance(); + } + + void TearDown() override { EXPECT_FALSE(fake_loop_.PendingTasks()); } + + bool SetNow(const base::Time::Exploded& exploded_now) { + base::Time now; + if (!base::Time::FromLocalExploded(exploded_now, &now)) + return false; + + test_clock_.SetNow(now); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(now); + return true; + } + + void AdvanceAfterTimestamp(const WeeklyTime& timestamp) { + const WeeklyTime now = WeeklyTime::FromTime(test_clock_.Now()); + const base::TimeDelta duration = + now.GetDurationTo(timestamp) + kDurationOffset; + test_clock_.Advance(duration); + FakeSystemState::Get()->fake_clock()->SetWallclockTime(test_clock_.Now()); + } + + void VerifyExpectationsOnDelegate() { + testing::Mock::VerifyAndClearExpectations(&mock_delegate_); + } + + void UpdateRestrictedIntervals(const WeeklyTimeIntervalVector& policy_value) { + auto* policy_variable = + fake_state_.device_policy_provider()->var_disallowed_time_intervals(); + policy_variable->reset(new WeeklyTimeIntervalVector(policy_value)); + policy_variable->NotifyValueChanged(); + } + + bool IsMonitoringInterval() { + return monitor_.has_value() && monitor_.value().IsMonitoringInterval(); + } + + void BuildMonitorAndVerify(const WeeklyTimeIntervalVector* policy_value, + bool expect_delegate_called, + bool expect_monitoring) { + if (expect_delegate_called) + EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(1); + else + EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(0); + + fake_state_.device_policy_provider() + ->var_disallowed_time_intervals() + ->reset(policy_value != nullptr + ? new WeeklyTimeIntervalVector(*policy_value) + : nullptr); + monitor_.emplace(fake_state_.device_policy_provider(), &mock_delegate_); + if (expect_delegate_called) + MessageLoopRunMaxIterations(MessageLoop::current(), 10); + VerifyExpectationsOnDelegate(); + + if (expect_monitoring) + EXPECT_TRUE(IsMonitoringInterval()); + else + EXPECT_FALSE(IsMonitoringInterval()); + } + + base::SimpleTestClock test_clock_; + FakeMessageLoop fake_loop_{&test_clock_}; + FakeState fake_state_; + MockUpdateTimeRestrictionsMonitorDelegate mock_delegate_; + base::Optional<UpdateTimeRestrictionsMonitor> monitor_; +}; + +TEST_F(UmUpdateTimeRestrictionsMonitorTest, PolicyIsNotSet) { + BuildMonitorAndVerify( + nullptr, /*expect_delegate_called=*/false, /*expect_monitoring=*/false); +} + +TEST_F(UmUpdateTimeRestrictionsMonitorTest, PolicyHasEmptyIntervalList) { + WeeklyTimeIntervalVector empty_policy; + BuildMonitorAndVerify(&empty_policy, + /*expect_delegate_called=*/false, + /*expect_monitoring=*/false); +} + +TEST_F(UmUpdateTimeRestrictionsMonitorTest, + CurrentTimeOutsideOfRestrictedInterval) { + ASSERT_TRUE(SetNow(kInitialTimeBeforeInterval)); + BuildMonitorAndVerify(&kTestTwoDisallowedTimeIntervals, + /*expect_delegate_called=*/false, + /*expect_monitoring=*/true); + + // Monitor should only notify start when passing start of interval. + EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(1); + AdvanceAfterTimestamp( + kTestTwoDisallowedTimeIntervals[current_restricted_interval_index] + .start()); + MessageLoopRunMaxIterations(MessageLoop::current(), 10); + VerifyExpectationsOnDelegate(); +} + +TEST_F(UmUpdateTimeRestrictionsMonitorTest, + CurrentTimeWithinRestrictedInterval) { + // Monitor should notify start when it is built with current + // time within interval. + ASSERT_TRUE(SetNow(kInitialTimeWithinInterval)); + BuildMonitorAndVerify(&kTestTwoDisallowedTimeIntervals, + /*expect_delegate_called=*/true, + /*expect_monitoring=*/false); +} + +TEST_F(UmUpdateTimeRestrictionsMonitorTest, + PolicyChangeFromNotSetToOutsideInterval) { + // Build monitor with empty initial list of intervals. + BuildMonitorAndVerify( + nullptr, /*expect_delegate_called=*/false, /*expect_monitoring=*/false); + + // Monitor should not do any notification right after intervals update. + ASSERT_TRUE(SetNow(kInitialTimeBeforeInterval)); + EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(0); + UpdateRestrictedIntervals(kTestTwoDisallowedTimeIntervals); + MessageLoopRunMaxIterations(MessageLoop::current(), 10); + VerifyExpectationsOnDelegate(); + EXPECT_TRUE(IsMonitoringInterval()); + + // Advance time within new interval and check that notification happen. + EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(1); + AdvanceAfterTimestamp( + kTestTwoDisallowedTimeIntervals[current_restricted_interval_index] + .start()); + MessageLoopRunMaxIterations(MessageLoop::current(), 10); + VerifyExpectationsOnDelegate(); +} + +TEST_F(UmUpdateTimeRestrictionsMonitorTest, + PolicyChangeFromNotSetToWithinInterval) { + // Build monitor with empty initial list of intervals. + BuildMonitorAndVerify( + nullptr, /*expect_delegate_called=*/false, /*expect_monitoring=*/false); + + // Advance time inside upcoming new interval and update the intervals. + // Monitor should immediately notify about started interval. + ASSERT_TRUE(SetNow(kInitialTimeWithinInterval)); + EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(1); + UpdateRestrictedIntervals(kTestTwoDisallowedTimeIntervals); + MessageLoopRunMaxIterations(MessageLoop::current(), 10); + VerifyExpectationsOnDelegate(); +} + +TEST_F(UmUpdateTimeRestrictionsMonitorTest, + PolicyChangeFromNotSetToEmptyInterval) { + BuildMonitorAndVerify( + nullptr, /*expect_delegate_called=*/false, /*expect_monitoring=*/false); + + EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(0); + UpdateRestrictedIntervals(WeeklyTimeIntervalVector()); + MessageLoopRunMaxIterations(MessageLoop::current(), 10); + VerifyExpectationsOnDelegate(); + EXPECT_FALSE(IsMonitoringInterval()); +} + +TEST_F(UmUpdateTimeRestrictionsMonitorTest, + PolicyChangeFromOneOutsideIntervalToAnother) { + // Build monitor with current time outside the intervals. + BuildMonitorAndVerify(&kTestTwoDisallowedTimeIntervals, + /*expect_delegate_called=*/false, + /*expect_monitoring=*/true); + + // Update the intervals to outide of current time and no notification should + // happen yet. + EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(0); + UpdateRestrictedIntervals(kTestOneDisallowedTimeIntervals); + MessageLoopRunMaxIterations(MessageLoop::current(), 10); + VerifyExpectationsOnDelegate(); + + // Advance time within new interval. Monitor should notify about started + // interval. + EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(1); + AdvanceAfterTimestamp( + kTestOneDisallowedTimeIntervals[current_restricted_interval_index] + .start()); + MessageLoopRunMaxIterations(MessageLoop::current(), 10); + VerifyExpectationsOnDelegate(); +} + +TEST_F(UmUpdateTimeRestrictionsMonitorTest, + PolicyChangeFromOutsideIntervalToWithin) { + ASSERT_TRUE(SetNow(kInitialTimeWithinInterval)); + + // Build monitor with current time outside the intervals. + BuildMonitorAndVerify(&kTestOneDisallowedTimeIntervals, + /*expect_delegate_called=*/false, + /*expect_monitoring=*/true); + + // Update interval such that current time is within it. Monitor should notify + // about started interval. + EXPECT_CALL(mock_delegate_, OnRestrictedIntervalStarts()).Times(1); + UpdateRestrictedIntervals(kTestTwoDisallowedTimeIntervals); + MessageLoopRunMaxIterations(MessageLoop::current(), 10); + VerifyExpectationsOnDelegate(); +} + +} // namespace chromeos_update_manager diff --git a/update_manager/update_time_restrictions_policy_impl_unittest.cc b/update_manager/update_time_restrictions_policy_impl_unittest.cc index 74e7f3ca..f99a2854 100644 --- a/update_manager/update_time_restrictions_policy_impl_unittest.cc +++ b/update_manager/update_time_restrictions_policy_impl_unittest.cc @@ -60,7 +60,7 @@ class UmUpdateTimeRestrictionsPolicyImplTest : public UmPolicyTestBase { Time time; EXPECT_TRUE(Time::FromLocalExploded(exploded, &time)); - fake_clock_.SetWallclockTime(time); + fake_clock_->SetWallclockTime(time); SetUpDefaultTimeProvider(); fake_state_.device_policy_provider() ->var_disallowed_time_intervals() diff --git a/update_manager/updater_provider.h b/update_manager/updater_provider.h index cb626238..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 { @@ -115,6 +116,10 @@ class UpdaterProvider : public Provider { // for all updates. virtual Variable<UpdateRestrictions>* var_update_restrictions() = 0; + // A variable that returns the number of seconds for the first update check to + // happen. + virtual Variable<int64_t>* var_test_update_check_interval_timeout() = 0; + protected: UpdaterProvider() {} diff --git a/update_manager/variable.h b/update_manager/variable.h index 6c7d3506..9ac7dae6 100644 --- a/update_manager/variable.h +++ b/update_manager/variable.h @@ -83,6 +83,10 @@ class BaseVariable { // variable. In other case, it returns 0. base::TimeDelta GetPollInterval() const { return poll_interval_; } + // Returns true, if the value for this variable is expected to be missing + // sometimes so we can avoid printing confusing error logs. + bool IsMissingOk() const { return missing_ok_; } + // Adds and removes observers for value changes on the variable. This only // works for kVariableAsync variables since the other modes don't track value // changes. Adding the same observer twice has no effect. @@ -115,6 +119,8 @@ class BaseVariable { poll_interval_ = poll_interval; } + void SetMissingOk() { missing_ok_ = true; } + // Calls ValueChanged on all the observers. void NotifyValueChanged() { // Fire all the observer methods from the main loop as single call. In order @@ -140,7 +146,8 @@ class BaseVariable { : name_(name), mode_(mode), poll_interval_(mode == kVariableModePoll ? poll_interval - : base::TimeDelta()) {} + : base::TimeDelta()), + missing_ok_(false) {} void OnValueChangedNotification() { // A ValueChanged() method can change the list of observers, for example @@ -174,6 +181,9 @@ class BaseVariable { // The list of value changes observers. std::list<BaseVariable::ObserverInterface*> observer_list_; + // Defines whether this variable is expected to have no value. + bool missing_ok_; + DISALLOW_COPY_AND_ASSIGN(BaseVariable); }; diff --git a/update_metadata.proto b/update_metadata.proto index 9bc0d8a5..93e4e2e1 100644 --- a/update_metadata.proto +++ b/update_metadata.proto @@ -78,7 +78,7 @@ // new partition. // - ZERO: Write zeros to the destination dst_extents. // - DISCARD: Discard the destination dst_extents blocks on the physical medium. -// the data read from those block is undefined. +// the data read from those blocks is undefined. // - REPLACE_XZ: Replace the dst_extents with the contents of the attached // xz file after decompression. The xz file should only use crc32 or no crc at // all to be compatible with xz-embedded. @@ -153,35 +153,35 @@ message PartitionInfo { // // All fields will be set, if this message is present. message ImageInfo { - optional string board = 1; - optional string key = 2; - optional string channel = 3; - optional string version = 4; + optional string board = 1 [deprecated = true]; + optional string key = 2 [deprecated = true]; + optional string channel = 3 [deprecated = true]; + optional string version = 4 [deprecated = true]; // If these values aren't present, they should be assumed to match // the equivalent value above. They are normally only different for // special image types such as nplusone images. - optional string build_channel = 5; - optional string build_version = 6; + optional string build_channel = 5 [deprecated = true]; + optional string build_version = 6 [deprecated = true]; } message InstallOperation { enum Type { - REPLACE = 0; // Replace destination extents w/ attached data - REPLACE_BZ = 1; // Replace destination extents w/ attached bzipped data - MOVE = 2 [deprecated = true]; // Move source extents to destination extents - BSDIFF = 3 [deprecated = true]; // The data is a bsdiff binary diff + REPLACE = 0; // Replace destination extents w/ attached data. + REPLACE_BZ = 1; // Replace destination extents w/ attached bzipped data. + MOVE = 2 [deprecated = true]; // Move source extents to target extents. + BSDIFF = 3 [deprecated = true]; // The data is a bsdiff binary diff. // On minor version 2 or newer, these operations are supported: - SOURCE_COPY = 4; // Copy from source to target partition - SOURCE_BSDIFF = 5; // Like BSDIFF, but read from source partition + SOURCE_COPY = 4; // Copy from source to target partition + SOURCE_BSDIFF = 5; // Like BSDIFF, but read from source partition // On minor version 3 or newer and on major version 2 or newer, these // operations are supported: - REPLACE_XZ = 8; // Replace destination extents w/ attached xz data. + REPLACE_XZ = 8; // Replace destination extents w/ attached xz data. // On minor version 4 or newer, these operations are supported: - ZERO = 6; // Write zeros in the destination. + ZERO = 6; // Write zeros in the destination. DISCARD = 7; // Discard the destination blocks, reading as undefined. BROTLI_BSDIFF = 10; // Like SOURCE_BSDIFF, but compressed with brotli. @@ -225,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 @@ -288,6 +304,21 @@ 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; + + // Estimated size for COW image. This is used by libsnapshot + // as a hint. If set to 0, libsnapshot should use alternative + // methods for estimating size. + optional uint64 estimate_cow_size = 19; } message DynamicPartitionGroup { @@ -317,14 +348,44 @@ message DynamicPartitionMetadata { // partitions if possible. If this is unset, the update_engine daemon MUST // NOT create snapshots for dynamic partitions. optional bool snapshot_enabled = 2; + + // If this is set to false, update_engine should not use VABC regardless. If + // this is set to true, update_engine may choose to use VABC if device + // supports it, but not guaranteed. + // VABC stands for Virtual AB Compression + optional bool vabc_enabled = 3; + + // The compression algorithm used by VABC. Available ones are "gz", "brotli". + // See system/core/fs_mgr/libsnapshot/cow_writer.cpp for available options, + // as this parameter is ultimated forwarded to libsnapshot's CowWriter + optional string vabc_compression_param = 4; + + // COW version used by VABC. The represents the major version in the COW + // header + optional uint32 cow_version = 5; +} + +// Definition has been duplicated from +// $ANDROID_BUILD_TOP/build/tools/releasetools/ota_metadata.proto. Keep in sync. +message ApexInfo { + optional string package_name = 1; + optional int64 version = 2; + optional bool is_compressed = 3; + optional int64 decompressed_size = 4; +} + +// Definition has been duplicated from +// $ANDROID_BUILD_TOP/build/tools/releasetools/ota_metadata.proto. Keep in sync. +message ApexMetadata { + repeated ApexInfo apex_info = 1; } message DeltaArchiveManifest { // Only present in major version = 1. List of install operations for the // kernel and rootfs partitions. For major version = 2 see the |partitions| // field. - repeated InstallOperation install_operations = 1; - repeated InstallOperation kernel_install_operations = 2; + repeated InstallOperation install_operations = 1 [deprecated = true]; + repeated InstallOperation kernel_install_operations = 2 [deprecated = true]; // (At time of writing) usually 4096 optional uint32 block_size = 3 [default = 4096]; @@ -339,15 +400,15 @@ message DeltaArchiveManifest { // Only present in major version = 1. Partition metadata used to validate the // update. For major version = 2 see the |partitions| field. - optional PartitionInfo old_kernel_info = 6; - optional PartitionInfo new_kernel_info = 7; - optional PartitionInfo old_rootfs_info = 8; - optional PartitionInfo new_rootfs_info = 9; + optional PartitionInfo old_kernel_info = 6 [deprecated = true]; + optional PartitionInfo new_kernel_info = 7 [deprecated = true]; + optional PartitionInfo old_rootfs_info = 8 [deprecated = true]; + optional PartitionInfo new_rootfs_info = 9 [deprecated = true]; // old_image_info will only be present for delta images. - optional ImageInfo old_image_info = 10; + optional ImageInfo old_image_info = 10 [deprecated = true]; - optional ImageInfo new_image_info = 11; + optional ImageInfo new_image_info = 11 [deprecated = true]; // The minor version, also referred as "delta version", of the payload. // Minor version 0 is full payload, everything else is delta payload. @@ -367,4 +428,11 @@ 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; + + // Information on compressed APEX to figure out how much space is required for + // their decompression + repeated ApexInfo apex_info = 17; } diff --git a/update_status_utils.cc b/update_status_utils.cc index 11fd2995..a702c61a 100644 --- a/update_status_utils.cc +++ b/update_status_utils.cc @@ -16,12 +16,32 @@ #include "update_engine/update_status_utils.h" #include <base/logging.h> +#include <base/strings/string_number_conversions.h> +#include <brillo/key_value_store.h> #include <update_engine/dbus-constants.h> +using brillo::KeyValueStore; +using std::string; +using update_engine::UpdateEngineStatus; using update_engine::UpdateStatus; namespace chromeos_update_engine { +namespace { + +// Note: Do not change these, autotest depends on these string variables being +// exactly these matches. +const char kCurrentOp[] = "CURRENT_OP"; +const char kIsInstall[] = "IS_INSTALL"; +const char kIsEnterpriseRollback[] = "IS_ENTERPRISE_ROLLBACK"; +const char kLastCheckedTime[] = "LAST_CHECKED_TIME"; +const char kNewSize[] = "NEW_SIZE"; +const char kNewVersion[] = "NEW_VERSION"; +const char kProgress[] = "PROGRESS"; +const char kWillPowerwashAfterReboot[] = "WILL_POWERWASH_AFTER_REBOOT"; + +} // namespace + const char* UpdateStatusToString(const UpdateStatus& status) { switch (status) { case UpdateStatus::IDLE: @@ -54,45 +74,23 @@ const char* UpdateStatusToString(const UpdateStatus& status) { return nullptr; } -bool StringToUpdateStatus(const std::string& s, UpdateStatus* status) { - if (s == update_engine::kUpdateStatusIdle) { - *status = UpdateStatus::IDLE; - return true; - } else if (s == update_engine::kUpdateStatusCheckingForUpdate) { - *status = UpdateStatus::CHECKING_FOR_UPDATE; - return true; - } else if (s == update_engine::kUpdateStatusUpdateAvailable) { - *status = UpdateStatus::UPDATE_AVAILABLE; - return true; - } else if (s == update_engine::kUpdateStatusNeedPermissionToUpdate) { - *status = UpdateStatus::NEED_PERMISSION_TO_UPDATE; - return true; - } else if (s == update_engine::kUpdateStatusDownloading) { - *status = UpdateStatus::DOWNLOADING; - return true; - } else if (s == update_engine::kUpdateStatusVerifying) { - *status = UpdateStatus::VERIFYING; - return true; - } else if (s == update_engine::kUpdateStatusFinalizing) { - *status = UpdateStatus::FINALIZING; - return true; - } else if (s == update_engine::kUpdateStatusUpdatedNeedReboot) { - *status = UpdateStatus::UPDATED_NEED_REBOOT; - return true; - } else if (s == update_engine::kUpdateStatusReportingErrorEvent) { - *status = UpdateStatus::REPORTING_ERROR_EVENT; - return true; - } else if (s == update_engine::kUpdateStatusAttemptingRollback) { - *status = UpdateStatus::ATTEMPTING_ROLLBACK; - return true; - } else if (s == update_engine::kUpdateStatusDisabled) { - *status = UpdateStatus::DISABLED; - return true; - } else if (s == update_engine::kUpdateStatusCleanupPreviousUpdate) { - *status = UpdateStatus::CLEANUP_PREVIOUS_UPDATE; - return true; - } - return false; +string UpdateEngineStatusToString(const UpdateEngineStatus& status) { + KeyValueStore key_value_store; + + key_value_store.SetString(kLastCheckedTime, + base::NumberToString(status.last_checked_time)); + key_value_store.SetString(kProgress, base::NumberToString(status.progress)); + key_value_store.SetString(kNewSize, + base::NumberToString(status.new_size_bytes)); + key_value_store.SetString(kCurrentOp, UpdateStatusToString(status.status)); + key_value_store.SetString(kNewVersion, status.new_version); + key_value_store.SetBoolean(kIsEnterpriseRollback, + status.is_enterprise_rollback); + key_value_store.SetBoolean(kIsInstall, status.is_install); + key_value_store.SetBoolean(kWillPowerwashAfterReboot, + status.will_powerwash_after_reboot); + + return key_value_store.SaveToString(); } } // namespace chromeos_update_engine diff --git a/update_status_utils.h b/update_status_utils.h index 30ae53b7..1e3fdde5 100644 --- a/update_status_utils.h +++ b/update_status_utils.h @@ -25,8 +25,8 @@ namespace chromeos_update_engine { const char* UpdateStatusToString(const update_engine::UpdateStatus& status); -bool StringToUpdateStatus(const std::string& update_status_as_string, - update_engine::UpdateStatus* status); +std::string UpdateEngineStatusToString( + const update_engine::UpdateEngineStatus& status); } // namespace chromeos_update_engine diff --git a/update_status_utils_unittest.cc b/update_status_utils_unittest.cc new file mode 100644 index 00000000..228201c8 --- /dev/null +++ b/update_status_utils_unittest.cc @@ -0,0 +1,53 @@ +// +// 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/update_status_utils.h" + +#include <string> + +#include <gtest/gtest.h> + +using std::string; + +namespace chromeos_update_engine { + +TEST(UpdateStatusUtilsTest, UpdateEngineStatusToStringTest) { + // Keep field assignments in same order as they were declared, + // to prevent compiler warning, -Wreorder-init-fields. + update_engine::UpdateEngineStatus update_engine_status = { + .last_checked_time = 156000000, + .status = update_engine::UpdateStatus::CHECKING_FOR_UPDATE, + .progress = 0.5, + .new_size_bytes = 888, + .new_version = "12345.0.0", + .is_enterprise_rollback = true, + .is_install = true, + .will_powerwash_after_reboot = true, + }; + string print = + R"(CURRENT_OP=UPDATE_STATUS_CHECKING_FOR_UPDATE +IS_ENTERPRISE_ROLLBACK=true +IS_INSTALL=true +LAST_CHECKED_TIME=156000000 +NEW_SIZE=888 +NEW_VERSION=12345.0.0 +PROGRESS=0.5 +WILL_POWERWASH_AFTER_REBOOT=true +)"; + EXPECT_EQ(print, UpdateEngineStatusToString(update_engine_status)); +} + +} // namespace chromeos_update_engine |